Skip to content

Commit 7c12942

Browse files
codelogicjbdeboer
authored andcommitted
feat(platform): Make angular invoke web_component polyfills for browsers without native web_component implementations.
1 parent 1a05a02 commit 7c12942

14 files changed

+581
-14
lines changed

example/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dependencies:
55
path: ../
66
browser: any
77
unittest: any
8+
web_components: any
89

910
transformers:
1011
- angular
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
:host {
3+
border-top: 2px solid white;
4+
border-bottom: 2px solid white;
5+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
:host {
2+
display: block;
3+
margin-top: 5px;
4+
}
5+
6+
/* This must be shimmed with a polymer shim rule */
7+
polyfill-next-selector { content: ':host span:not([:host])'; }
8+
::content span {
9+
background-color: black;
10+
}
11+
12+
my-button::shadow .custom-class {
13+
background-color: rgba(0,0,0, .5);
14+
}
15+
16+
span {
17+
font-weight: bold;
18+
}
19+
20+
.custom-component {
21+
color: white;
22+
padding: 5px;
23+
}
24+
25+
.custom-component a {
26+
color: white;
27+
}
28+
29+
.red {
30+
background-color: #FF1F1F;
31+
}
32+
33+
.green {
34+
background-color: #1BC123;
35+
}
36+
37+
.blue {
38+
background-color: #4D90FE;
39+
}
40+
41+
.grey {
42+
background-color: #E0E0E0;
43+
}

example/web/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<li><a href="bouncing_balls.html">bouncing_balls.html</a></li>
66
<li><a href="hello_world.html">hello_world.html</a></li>
77
<li><a href="todo.html">todo.html</a></li>
8+
<li><a href="shadow_dom_components.html">shadow_dom_components.html</a></li>
89
</ul>
910
</body>
1011
</html>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import 'dart:html';
2+
3+
import 'package:angular/angular.dart';
4+
import 'package:angular/animate/module.dart';
5+
import 'package:angular/application_factory.dart';
6+
import 'package:di/di.dart';
7+
8+
main() {
9+
var app = applicationFactory();
10+
app.modules.add(new Module()
11+
..bind(MyComponent)
12+
..bind(BracketButton));
13+
app.selector("body");
14+
app.run();
15+
}
16+
17+
@Component(
18+
selector: "my-component",
19+
publishAs: "ctrl",
20+
template: """
21+
<div class="custom-component" ng-class="ctrl.color">
22+
<span>Shadow [</span>
23+
<content></content>
24+
<span>]</span>
25+
<a href="#" ng-click="ctrl.on=!ctrl.on"><my-button>
26+
Toggle</my-button></a>
27+
<span ng-if="ctrl.on">off</span>
28+
<span ng-if="!ctrl.on">on</span>
29+
</div>
30+
""",
31+
cssUrl: "/css/shadow_dom_components.css")
32+
class MyComponent {
33+
@NgAttr('color')
34+
String color;
35+
36+
bool on = false;
37+
}
38+
39+
@Component(
40+
selector: "my-button",
41+
template: """<span class="custom-bracket">[[[<content>
42+
</content>]]]</span>""",
43+
cssUrl: "/css/shadow_dom_bracket.css")
44+
class BracketButton {
45+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!DOCTYPE html>
2+
3+
<html>
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Web Components test page</title>
8+
<style>
9+
body {
10+
margin: 0;
11+
/* Extra spacing at top and bottom */
12+
padding: 20px 20px 0 20px;
13+
min-height: 100%;
14+
font-family: sans-serif;
15+
}
16+
[ng-cloak] {
17+
display: none;
18+
}
19+
</style>
20+
21+
<script src="packages/web_components/platform.js"></script>
22+
<script src="packages/web_components/dart_support.js"></script>
23+
</head>
24+
25+
<body>
26+
<p ng-if="false">Just waiting for this demo app to load...</p>
27+
<div class="content" ng-cloak>
28+
<h1>Hi</h1>
29+
30+
<p>There should be <strong>three</strong> components here. One red,
31+
one green, one blue:</p>
32+
33+
<my-component color="red">I'm a red component</my-component>
34+
<my-component color="green">I'm a green component</my-component>
35+
<my-component color="blue">I'm a blue component</my-component>
36+
37+
<br />
38+
<my-component color="grey">I'm a
39+
<span>content span</span></my-component>
40+
<br />
41+
<div class="red">I'm just a div with the <code>.red</code> class</div>
42+
<div class="green">I'm just a div with the <code>.green</code> class</div>
43+
<div class="blue">I'm just a div with the <code>.blue</code> class</div>
44+
<br />
45+
<h4>Nesting</h4>
46+
47+
48+
<my-component color="red">
49+
<my-component color="green">
50+
<my-component color="blue">
51+
I'm a blue component.
52+
</my-component>
53+
</my-component>
54+
</my-component>
55+
56+
<my-component color="green">
57+
<my-component color="blue">
58+
<my-component color="red">
59+
I'm a red component.
60+
</my-component>
61+
</my-component>
62+
</my-component>
63+
64+
<my-component color="blue">
65+
<my-component color="red">
66+
<my-component color="green">
67+
I'm a green component.
68+
</my-component>
69+
</my-component>
70+
</my-component>
71+
</div>
72+
<script type="application/dart" src="shadow_dom_components.dart"></script>
73+
<script src="packages/browser/dart.js"></script>
74+
</body>
75+
</html>

lib/core/module.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,11 @@ export "package:angular/core_dom/module_internal.dart" show
6060

6161
export "package:angular/core/module_internal.dart" show
6262
CacheStats,
63+
ComponentCssRewriter,
6364
ExceptionHandler,
6465
Interpolate,
6566
VmTurnZone,
67+
WebPlatform,
6668
PrototypeMap,
6769
RootScope,
6870
Scope,

lib/core_dom/module_internal.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ library angular.core.dom_internal;
33
import 'dart:async' as async;
44
import 'dart:convert' show JSON;
55
import 'dart:html' as dom;
6+
import 'dart:js' as js;
67

78
import 'package:di/di.dart';
89
import 'package:perf_api/perf_api.dart';
@@ -32,6 +33,7 @@ part 'event_handler.dart';
3233
part 'http.dart';
3334
part 'mustache.dart';
3435
part 'node_cursor.dart';
36+
part 'web_platform.dart';
3537
part 'selector.dart';
3638
part 'shadow_dom_component_factory.dart';
3739
part 'shadowless_shadow_root.dart';
@@ -63,6 +65,8 @@ class CoreDomModule extends Module {
6365
bind(TranscludingComponentFactory);
6466
bind(Content);
6567
bind(ContentPort, toValue: null);
68+
bind(ComponentCssRewriter);
69+
bind(WebPlatform);
6670

6771
bind(Http);
6872
bind(UrlRewriter);

lib/core_dom/shadow_dom_component_factory.dart

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ShadowDomComponentFactory implements ComponentFactory {
3131

3232
ShadowDomComponentFactory(this._expando);
3333

34-
final Map<String, async.Future<dom.StyleElement>> _styleElementCache = {};
34+
final Map<_ComponentAssetKey, async.Future<dom.StyleElement>> _styleElementCache = {};
3535

3636

3737

@@ -45,8 +45,15 @@ class ShadowDomComponentFactory implements ComponentFactory {
4545
DirectiveMap directives = injector.getByKey(_DIRECTIVE_MAP_KEY);
4646
NgBaseCss baseCss = component.useNgBaseCss ? injector.getByKey(_NG_BASE_CSS_KEY) : null;
4747
// This is a bit of a hack since we are returning different type then we are.
48-
var componentFactory = new _ComponentFactory(node, ref.typeKey, component,
49-
injector.getByKey(_NODE_TREE_SANITIZER_KEY), _expando, baseCss, _styleElementCache);
48+
var componentFactory = new _ComponentFactory(node,
49+
ref.typeKey,
50+
component,
51+
injector.getByKey(_NODE_TREE_SANITIZER_KEY),
52+
injector.getByKey(_WEB_PLATFORM_KEY),
53+
injector.getByKey(_COMPONENT_CSS_REWRITER_KEY),
54+
_expando,
55+
baseCss,
56+
_styleElementCache);
5057
var controller = componentFactory.call(injector, scope, viewCache, http, templateCache,
5158
directives);
5259

@@ -70,15 +77,19 @@ class _ComponentFactory implements Function {
7077
final dom.NodeTreeSanitizer treeSanitizer;
7178
final Expando _expando;
7279
final NgBaseCss _baseCss;
73-
final Map<String, async.Future<dom.StyleElement>> _styleElementCache;
80+
final Map<_ComponentAssetKey, async.Future<dom.StyleElement>>
81+
_styleElementCache;
82+
final ComponentCssRewriter componentCssRewriter;
83+
final WebPlatform platform;
7484

7585
dom.ShadowRoot shadowDom;
7686
Scope shadowScope;
7787
Injector shadowInjector;
7888
var controller;
7989

8090
_ComponentFactory(this.element, this.typeKey, this.component, this.treeSanitizer,
81-
this._expando, this._baseCss, this._styleElementCache);
91+
this.platform, this.componentCssRewriter, this._expando,
92+
this._baseCss, this._styleElementCache);
8293

8394
dynamic call(Injector injector, Scope scope,
8495
ViewCache viewCache, Http http, TemplateCache templateCache,
@@ -97,22 +108,57 @@ class _ComponentFactory implements Function {
97108
var cssUrls = _baseCss != null ?
98109
([]..addAll(_baseCss.urls)..addAll(component.cssUrls)) :
99110
component.cssUrls;
111+
var tag = element.tagName.toLowerCase();
100112
if (cssUrls.isNotEmpty) {
101-
cssFutures = cssUrls.map((cssUrl) => _styleElementCache.putIfAbsent(cssUrl, () =>
113+
cssFutures = cssUrls.map((cssUrl) => _styleElementCache.putIfAbsent(
114+
new _ComponentAssetKey(tag, cssUrl), () =>
102115
http.get(cssUrl, cache: templateCache)
103116
.then((resp) => resp.responseText,
104117
onError: (e) => '/*\n$e\n*/\n')
105-
.then((styleContent) => new dom.StyleElement()..appendText(styleContent))
118+
.then((String css) {
119+
120+
// Shim CSS if required
121+
if (platform.cssShimRequired) {
122+
css = platform.shimCss(css, selector: tag, cssUrl: cssUrl);
123+
}
124+
125+
// If a css rewriter is installed, run the css through a rewriter
126+
var styleElement = new dom.StyleElement()
127+
..appendText(componentCssRewriter(css, selector: tag,
128+
cssUrl: cssUrl));
129+
130+
// ensure there are no invalid tags or modifications
131+
treeSanitizer.sanitizeTree(styleElement);
132+
133+
// If the css shim is required, it means that scoping does not
134+
// work, and adding the style to the head of the document is
135+
// preferrable.
136+
if (platform.cssShimRequired) {
137+
dom.document.head.append(styleElement);
138+
}
139+
140+
return styleElement;
141+
})
106142
)).toList();
107143
} else {
108144
cssFutures = [new async.Future.value(null)];
109145
}
110-
var viewFuture = ComponentFactory._viewFuture(component, viewCache, directives);
146+
147+
var platformViewCache = new PlatformViewCache(viewCache, tag, platform);
148+
149+
var viewFuture = ComponentFactory._viewFuture(component, platformViewCache,
150+
directives);
151+
111152
TemplateLoader templateLoader = new TemplateLoader(
112153
async.Future.wait(cssFutures).then((Iterable<dom.StyleElement> cssList) {
113-
cssList
114-
.where((styleElement) => styleElement != null)
115-
.forEach((styleElement) => shadowDom.append(styleElement.clone(true)));
154+
// This prevents style duplication by only adding css to the shadow
155+
// root if there is a native implementation of shadow dom.
156+
if (!platform.cssShimRequired) {
157+
cssList.where((styleElement) => styleElement != null)
158+
.forEach((styleElement) {
159+
shadowDom.append(styleElement.clone(true));
160+
});
161+
}
116162
if (viewFuture != null) {
117163
return viewFuture.then((ViewFactory viewFactory) {
118164
return (!shadowScope.isAttached) ?
@@ -150,3 +196,33 @@ class _ComponentFactory implements Function {
150196
return shadowInjector;
151197
}
152198
}
199+
200+
class _ComponentAssetKey {
201+
final String tag;
202+
final String assetUrl;
203+
204+
final String _key;
205+
206+
_ComponentAssetKey(String tag, String assetUrl)
207+
: _key = "$tag|$assetUrl",
208+
this.tag = tag,
209+
this.assetUrl = assetUrl;
210+
211+
@override
212+
String toString() => _key;
213+
214+
@override
215+
int get hashCode => _key.hashCode;
216+
217+
bool operator ==(key) =>
218+
key is _ComponentAssetKey
219+
&& tag == key.tag
220+
&& assetUrl == key.assetUrl;
221+
}
222+
223+
@Injectable()
224+
class ComponentCssRewriter {
225+
String call(String css, { String selector, String cssUrl} ) {
226+
return css;
227+
}
228+
}

lib/core_dom/view_factory.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class WalkingViewFactory implements ViewFactory {
120120
@Injectable()
121121
class ViewCache {
122122
// _viewFactoryCache is unbounded
123-
final _viewFactoryCache = new LruCache<String, ViewFactory>();
123+
final viewFactoryCache = new LruCache<String, ViewFactory>();
124124
final Http http;
125125
final TemplateCache templateCache;
126126
final Compiler compiler;
@@ -129,12 +129,12 @@ class ViewCache {
129129
ViewCache(this.http, this.templateCache, this.compiler, this.treeSanitizer);
130130

131131
ViewFactory fromHtml(String html, DirectiveMap directives) {
132-
ViewFactory viewFactory = _viewFactoryCache.get(html);
132+
ViewFactory viewFactory = viewFactoryCache.get(html);
133133
if (viewFactory == null) {
134134
var div = new dom.DivElement();
135135
div.setInnerHtml(html, treeSanitizer: treeSanitizer);
136136
viewFactory = compiler(div.nodes, directives);
137-
_viewFactoryCache.put(html, viewFactory);
137+
viewFactoryCache.put(html, viewFactory);
138138
}
139139
return viewFactory;
140140
}

0 commit comments

Comments
 (0)