From 2b115da33f4fe46000ecf653f65b934aa5a84cc1 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Sat, 7 Jun 2014 15:55:43 -0700 Subject: [PATCH] perf(compiler): +6% Pre-compute ViewFactories, styles for components. This sped up the TreeComponent benchmark by 6%. --- lib/core_dom/element_binder.dart | 32 +- lib/core_dom/element_binder_builder.dart | 79 +++-- lib/core_dom/selector.dart | 6 +- .../shadow_dom_component_factory.dart | 299 ++++++++---------- .../transcluding_component_factory.dart | 66 +++- .../core_dom/element_binder_builder_spec.dart | 12 +- test/core_dom/selector_spec.dart | 6 +- test/directive/ng_base_css_spec.dart | 12 +- 8 files changed, 272 insertions(+), 240 deletions(-) diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index 558b56c06..ffe415da8 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -14,12 +14,10 @@ class TemplateElementBinder extends ElementBinder { return _directiveCache = [template]; } - TemplateElementBinder(perf, expando, parser, config, componentFactory, - transcludingComponentFactory, shadowDomComponentFactory, + TemplateElementBinder(perf, expando, parser, config, this.template, this.templateBinder, onEvents, bindAttrs, childMode) - : super(perf, expando, parser, config, componentFactory, - transcludingComponentFactory, shadowDomComponentFactory, + : super(perf, expando, parser, config, null, null, onEvents, bindAttrs, childMode); String toString() => "[TemplateElementBinder template:$template]"; @@ -47,26 +45,19 @@ class ElementBinder { final Parser _parser; final CompilerConfig _config; - // The default component factory - final ComponentFactory _componentFactory; - final TranscludingComponentFactory _transcludingComponentFactory; - final ShadowDomComponentFactory _shadowDomComponentFactory; final Map onEvents; final Map bindAttrs; // Member fields final decorators; - final DirectiveRef component; + final BoundComponentData componentData; // Can be either COMPILE_CHILDREN or IGNORE_CHILDREN final String childMode; ElementBinder(this._perf, this._expando, this._parser, this._config, - this._componentFactory, - this._transcludingComponentFactory, - this._shadowDomComponentFactory, - this.component, this.decorators, + this.componentData, this.decorators, this.onEvents, this.bindAttrs, this.childMode); final bool hasTemplate = false; @@ -77,7 +68,7 @@ class ElementBinder { var _directiveCache; List get _usableDirectiveRefs { if (_directiveCache != null) return _directiveCache; - if (component != null) return _directiveCache = new List.from(decorators)..add(component); + if (componentData != null) return _directiveCache = new List.from(decorators)..add(componentData.ref); return _directiveCache = decorators; } @@ -260,16 +251,9 @@ class ElementBinder { } nodesAttrsDirectives.add(ref); } else if (ref.annotation is Component) { - var factory; - var annotation = ref.annotation as Component; - if (annotation.useShadowDom == true) { - factory = _shadowDomComponentFactory; - } else if (annotation.useShadowDom == false) { - factory = _transcludingComponentFactory; - } else { - factory = _componentFactory; - } - nodeModule.bindByKey(ref.typeKey, toFactory: factory.call(node, ref), visibility: visibility); + assert(ref == componentData.ref); + + nodeModule.bindByKey(ref.typeKey, toFactory: componentData.factory.call(node), visibility: visibility); } else { nodeModule.bindByKey(ref.typeKey, visibility: visibility); } diff --git a/lib/core_dom/element_binder_builder.dart b/lib/core_dom/element_binder_builder.dart index 343906364..7c97648ab 100644 --- a/lib/core_dom/element_binder_builder.dart +++ b/lib/core_dom/element_binder_builder.dart @@ -6,29 +6,25 @@ class ElementBinderFactory { final Profiler _perf; final CompilerConfig _config; final Expando _expando; - final ComponentFactory _componentFactory; - final TranscludingComponentFactory _transcludingComponentFactory; - final ShadowDomComponentFactory _shadowDomComponentFactory; - final ASTParser _astParser; + final ASTParser astParser; + final ComponentFactory componentFactory; + final ShadowDomComponentFactory shadowDomComponentFactory; + final TranscludingComponentFactory transcludingComponentFactory; ElementBinderFactory(this._parser, this._perf, this._config, this._expando, - this._componentFactory, - this._transcludingComponentFactory, - this._shadowDomComponentFactory, - this._astParser); + this.astParser, this.componentFactory, this.shadowDomComponentFactory, this.transcludingComponentFactory); // TODO: Optimize this to re-use a builder. - ElementBinderBuilder builder(FormatterMap formatters) => - new ElementBinderBuilder(this, _astParser, formatters); + ElementBinderBuilder builder(FormatterMap formatters, DirectiveMap directives) => + new ElementBinderBuilder(this,formatters, directives); ElementBinder binder(ElementBinderBuilder b) => - new ElementBinder(_perf, _expando, _parser, _config, _componentFactory, - _transcludingComponentFactory, _shadowDomComponentFactory, - b.component, b.decorators, b.onEvents, b.bindAttrs, b.childMode); + + new ElementBinder(_perf, _expando, _parser, _config, + b.componentData, b.decorators, b.onEvents, b.bindAttrs, b.childMode); TemplateElementBinder templateBinder(ElementBinderBuilder b, ElementBinder transclude) => - new TemplateElementBinder(_perf, _expando, _parser, _config, _componentFactory, - _transcludingComponentFactory, _shadowDomComponentFactory, + new TemplateElementBinder(_perf, _expando, _parser, _config, b.template, transclude, b.onEvents, b.bindAttrs, b.childMode); } @@ -39,9 +35,9 @@ class ElementBinderFactory { class ElementBinderBuilder { static final RegExp _MAPPING = new RegExp(r'^(@|=>!|=>|<=>|&)\s*(.*)$'); - ElementBinderFactory _factory; - ASTParser _astParser; - FormatterMap _formatters; + final ElementBinderFactory _factory; + final DirectiveMap _directives; + final FormatterMap _formatters; /// "on-*" attribute names and values, added by a [DirectiveSelector] final onEvents = {}; @@ -50,12 +46,12 @@ class ElementBinderBuilder { final decorators = []; DirectiveRef template; - DirectiveRef component; + BoundComponentData componentData; // Can be either COMPILE_CHILDREN or IGNORE_CHILDREN String childMode = Directive.COMPILE_CHILDREN; - ElementBinderBuilder(this._factory, this._astParser, this._formatters); + ElementBinderBuilder(this._factory, this._formatters, this._directives); /** * Adds [DirectiveRef]s to this [ElementBinderBuilder]. @@ -73,7 +69,17 @@ class ElementBinderBuilder { if (annotation.children == Directive.TRANSCLUDE_CHILDREN) { template = ref; } else if (annotation is Component) { - component = ref; + ComponentFactory factory; + var annotation = ref.annotation as Component; + if (annotation.useShadowDom == true) { + factory = _factory.shadowDomComponentFactory; + } else if (annotation.useShadowDom == false) { + factory = _factory.transcludingComponentFactory; + } else { + factory = _factory.componentFactory; + } + + componentData = new BoundComponentData(ref, () => factory.bind(ref, _directives)); } else { decorators.add(ref); } @@ -92,14 +98,14 @@ class ElementBinderBuilder { var dstPath = match[2]; String dstExpression = dstPath.isEmpty ? attrName : dstPath; - AST dstAST = _astParser(dstExpression); // no formatters + AST dstAST = _factory.astParser(dstExpression); // no formatters // Look up the value of attrName and compute an AST AST ast; if (mode != '@' && mode != '&') { var value = attrName == "." ? ref.value : (ref.element as dom.Element).attributes[attrName]; if (value == null || value.isEmpty) { value = "''"; } - ast = _astParser(value, formatters: _formatters); + ast = _factory.astParser(value, formatters: _formatters); } ref.mappings.add(new MappingParts(attrName, ast, mode, dstAST, mapping)); @@ -113,3 +119,30 @@ class ElementBinderBuilder { return template == null ? elBinder : _factory.templateBinder(this, elBinder); } } + +/** + * Data used by the ComponentFactory to construct components. + */ +class BoundComponentData { + final DirectiveRef ref; + BoundComponentFactory _instance; + Function _gen; + BoundComponentFactory get factory { + if (_instance != null) return _instance; + _instance = _gen(); + _gen = null; // Clear the gen function for GC. + return _instance; + } + + Component get component => ref.annotation as Component; + @Deprecated('Use typeKey instead') + Type get type => ref.type; + Key get typeKey => ref.typeKey; + + + /** + * * [ref]: The components directive ref + * * [_gen]: A function which returns a [BoundComponentFactory]. Called lazily. + */ + BoundComponentData(this.ref, this._gen); +} diff --git a/lib/core_dom/selector.dart b/lib/core_dom/selector.dart index cc8d948e2..48e8b0c65 100644 --- a/lib/core_dom/selector.dart +++ b/lib/core_dom/selector.dart @@ -59,7 +59,7 @@ class DirectiveSelector { ElementBinder matchElement(dom.Node node) { assert(node is dom.Element); - ElementBinderBuilder builder = _binderFactory.builder(_formatters); + ElementBinderBuilder builder = _binderFactory.builder(_formatters, _directives); List<_ElementSelector> partialSelection; final classes = new Set(); final attrs = {}; @@ -129,7 +129,7 @@ class DirectiveSelector { } ElementBinder matchText(dom.Node node) { - ElementBinderBuilder builder = _binderFactory.builder(_formatters); + ElementBinderBuilder builder = _binderFactory.builder(_formatters, _directives); var value = node.nodeValue; for (var k = 0; k < textSelector.length; k++) { @@ -148,7 +148,7 @@ class DirectiveSelector { return builder.binder; } - ElementBinder matchComment(dom.Node node) => _binderFactory.builder(null).binder; + ElementBinder matchComment(dom.Node node) => _binderFactory.builder(null, null).binder; } /** diff --git a/lib/core_dom/shadow_dom_component_factory.dart b/lib/core_dom/shadow_dom_component_factory.dart index c3b1848fd..d68827379 100644 --- a/lib/core_dom/shadow_dom_component_factory.dart +++ b/lib/core_dom/shadow_dom_component_factory.dart @@ -1,7 +1,14 @@ part of angular.core.dom_internal; abstract class ComponentFactory { - FactoryFn call(dom.Node node, DirectiveRef ref); + BoundComponentFactory bind(DirectiveRef ref, directives); +} + +/** + * A Component factory with has been bound to a specific component type. + */ +abstract class BoundComponentFactory { + FactoryFn call(dom.Element element); static async.Future _viewFuture( Component component, ViewCache viewCache, DirectiveMap directives) { @@ -27,185 +34,159 @@ abstract class ComponentFactory { @Injectable() class ShadowDomComponentFactory implements ComponentFactory { - final Expando _expando; - final CompilerConfig _config; + final ViewCache viewCache; + final Http http; + final TemplateCache templateCache; + final WebPlatform platform; + final ComponentCssRewriter componentCssRewriter; + final dom.NodeTreeSanitizer treeSanitizer; + final Expando expando; + final CompilerConfig config; - ShadowDomComponentFactory(this._expando, this._config); + final Map<_ComponentAssetKey, async.Future> styleElementCache = {}; - final Map<_ComponentAssetKey, async.Future> _styleElementCache = {}; + ShadowDomComponentFactory(this.viewCache, this.http, this.templateCache, this.platform, this.componentCssRewriter, this.treeSanitizer, this.expando, this.config); - - - FactoryFn call(dom.Node node, DirectiveRef ref) { - return (Injector injector) { - var component = ref.annotation as Component; - Scope scope = injector.getByKey(SCOPE_KEY); - ViewCache viewCache = injector.getByKey(VIEW_CACHE_KEY); - Http http = injector.getByKey(HTTP_KEY); - TemplateCache templateCache = injector.getByKey(TEMPLATE_CACHE_KEY); - DirectiveMap directives = injector.getByKey(DIRECTIVE_MAP_KEY); - NgBaseCss baseCss = component.useNgBaseCss ? injector.getByKey(NG_BASE_CSS_KEY) : null; - // This is a bit of a hack since we are returning different type then we are. - var componentFactory = new _ComponentFactory(node, - ref.typeKey, - component, - injector.getByKey(NODE_TREE_SANITIZER_KEY), - injector.getByKey(WEB_PLATFORM_KEY), - injector.getByKey(COMPONENT_CSS_REWRITER_KEY), - _expando, - baseCss, - _styleElementCache, - _config); - var controller = componentFactory.call(injector, scope, viewCache, http, templateCache, - directives); - - componentFactory.shadowScope.context[component.publishAs] = controller; - return controller; - }; - } + bind(DirectiveRef ref, directives) => + new BoundShadowDomComponentFactory(this, ref, directives); } +class BoundShadowDomComponentFactory implements BoundComponentFactory { -/** - * ComponentFactory is responsible for setting up components. This includes - * the shadowDom, fetching template, importing styles, setting up attribute - * mappings, publishing the controller, and compiling and caching the template. - */ -class _ComponentFactory implements Function { + final ShadowDomComponentFactory _f; + final DirectiveRef _ref; + final DirectiveMap _directives; - final dom.Element element; - final Key typeKey; - final Component component; - final dom.NodeTreeSanitizer treeSanitizer; - final Expando _expando; - final NgBaseCss _baseCss; - final Map<_ComponentAssetKey, async.Future> - _styleElementCache; - final ComponentCssRewriter componentCssRewriter; - final WebPlatform platform; - final CompilerConfig _config; - - dom.ShadowRoot shadowDom; - Scope shadowScope; - Injector shadowInjector; - var controller; - - _ComponentFactory(this.element, this.typeKey, this.component, this.treeSanitizer, - this.platform, this.componentCssRewriter, this._expando, - this._baseCss, this._styleElementCache, this._config); - - dynamic call(Injector injector, Scope scope, - ViewCache viewCache, Http http, TemplateCache templateCache, - DirectiveMap directives) { - shadowDom = element.createShadowRoot() - ..applyAuthorStyles = component.applyAuthorStyles - ..resetStyleInheritance = component.resetStyleInheritance; - - shadowScope = scope.createChild({}); // Isolate - // TODO(pavelgj): fetching CSS with Http is mainly an attempt to - // work around an unfiled Chrome bug when reloading same CSS breaks - // styles all over the page. We shouldn't be doing browsers work, - // so change back to using @import once Chrome bug is fixed or a - // better work around is found. - Iterable> cssFutures; - var cssUrls = _baseCss != null ? - ([]..addAll(_baseCss.urls)..addAll(component.cssUrls)) : - component.cssUrls; - var tag = element.tagName.toLowerCase(); - if (cssUrls.isNotEmpty) { - cssFutures = cssUrls.map((cssUrl) => _styleElementCache.putIfAbsent( - new _ComponentAssetKey(tag, cssUrl), () => - http.get(cssUrl, cache: templateCache) - .then((resp) => resp.responseText, - onError: (e) => '/*\n$e\n*/\n') - .then((String css) { + Component get _component => _ref.annotation as Component; - // Shim CSS if required - if (platform.cssShimRequired) { - css = platform.shimCss(css, selector: tag, cssUrl: cssUrl); - } + String _tag; + async.Future> _styleElementsFuture; + async.Future _viewFuture; - // If a css rewriter is installed, run the css through a rewriter - var styleElement = new dom.StyleElement() - ..appendText(componentCssRewriter(css, selector: tag, - cssUrl: cssUrl)); + BoundShadowDomComponentFactory(this._f, this._ref, this._directives) { + _tag = _component.selector.toLowerCase(); + _styleElementsFuture = async.Future.wait(_component.cssUrls.map(_styleFuture)); - // ensure there are no invalid tags or modifications - treeSanitizer.sanitizeTree(styleElement); + _viewFuture = BoundComponentFactory._viewFuture( + _component, + new PlatformViewCache(_f.viewCache, _tag, _f.platform), + _directives); + } - // If the css shim is required, it means that scoping does not - // work, and adding the style to the head of the document is - // preferrable. - if (platform.cssShimRequired) { - dom.document.head.append(styleElement); - } + async.Future _styleFuture(cssUrl) { + Http http = _f.http; + TemplateCache templateCache = _f.templateCache; + WebPlatform platform = _f.platform; + ComponentCssRewriter componentCssRewriter = _f.componentCssRewriter; + dom.NodeTreeSanitizer treeSanitizer = _f.treeSanitizer; - return styleElement; - }) - )).toList(); - } else { - cssFutures = [new async.Future.value(null)]; - } + return _f.styleElementCache.putIfAbsent( + new _ComponentAssetKey(_tag, cssUrl), () => + http.get(cssUrl, cache: templateCache) + .then((resp) => resp.responseText, + onError: (e) => '/*\n$e\n*/\n') + .then((String css) { - var platformViewCache = new PlatformViewCache(viewCache, tag, platform); + // Shim CSS if required + if (platform.cssShimRequired) { + css = platform.shimCss(css, selector: _tag, cssUrl: cssUrl); + } - var viewFuture = ComponentFactory._viewFuture(component, platformViewCache, - directives); + // If a css rewriter is installed, run the css through a rewriter + var styleElement = new dom.StyleElement() + ..appendText(componentCssRewriter(css, selector: _tag, + cssUrl: cssUrl)); - TemplateLoader templateLoader = new TemplateLoader( - async.Future.wait(cssFutures).then((Iterable cssList) { - // This prevents style duplication by only adding css to the shadow - // root if there is a native implementation of shadow dom. - if (!platform.cssShimRequired) { - cssList.where((styleElement) => styleElement != null) - .forEach((styleElement) { - shadowDom.append(styleElement.clone(true)); - }); - } - if (viewFuture != null) { - return viewFuture.then((ViewFactory viewFactory) { - return (!shadowScope.isAttached) ? - shadowDom : - attachViewToShadowDom(viewFactory); - }); + // ensure there are no invalid tags or modifications + treeSanitizer.sanitizeTree(styleElement); + + // If the css shim is required, it means that scoping does not + // work, and adding the style to the head of the document is + // preferrable. + if (platform.cssShimRequired) { + dom.document.head.append(styleElement); + return null; } - return shadowDom; - })); - controller = createShadowInjector(injector, templateLoader).getByKey(typeKey); - ComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); - return controller; - } - dom.ShadowRoot attachViewToShadowDom(ViewFactory viewFactory) { - var view = viewFactory(shadowInjector); - shadowDom.nodes.addAll(view.nodes); - return shadowDom; + return styleElement; + }) + ); } - Injector createShadowInjector(injector, TemplateLoader templateLoader) { - var probe; - var shadowModule = new Module() - ..bindByKey(typeKey) - ..bindByKey(NG_ELEMENT_KEY) - ..bindByKey(EVENT_HANDLER_KEY, toImplementation: ShadowRootEventHandler) - ..bindByKey(SCOPE_KEY, toValue: shadowScope) - ..bindByKey(TEMPLATE_LOADER_KEY, toValue: templateLoader) - ..bindByKey(SHADOW_ROOT_KEY, toValue: shadowDom); - if (_config.elementProbeEnabled) { - shadowModule.bindByKey(ELEMENT_PROBE_KEY, toFactory: (_) => probe); - } + FactoryFn call(dom.Element element) { + return (Injector injector) { + Scope scope = injector.getByKey(SCOPE_KEY); + NgBaseCss baseCss = _component.useNgBaseCss ? injector.getByKey(NG_BASE_CSS_KEY) : null; + + var shadowDom = element.createShadowRoot() + ..applyAuthorStyles = _component.applyAuthorStyles + ..resetStyleInheritance = _component.resetStyleInheritance; + + var shadowScope = scope.createChild({}); // Isolate + + async.Future> cssFuture; + if (baseCss != null) { + cssFuture = async.Future.wait( + [async.Future.wait(baseCss.urls.map(_styleFuture)), _styleElementsFuture]) + .then((twoLists) { + assert(twoLists.length == 2); + return []..addAll(twoLists[0])..addAll(twoLists[1]); + }); + } else { + cssFuture = _styleElementsFuture; + } - shadowInjector = injector.createChild([shadowModule], name: SHADOW_DOM_INJECTOR_NAME); + Injector shadowInjector; - if (_config.elementProbeEnabled) { - probe = _expando[shadowDom] = - new ElementProbe(injector.getByKey(ELEMENT_PROBE_KEY), - shadowDom, shadowInjector, shadowScope); - shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) { - _expando[shadowDom] = null; - }); - } - return shadowInjector; + TemplateLoader templateLoader = new TemplateLoader( + cssFuture.then((Iterable cssList) { + cssList + .where((styleElement) => styleElement != null) + .forEach((styleElement) { + shadowDom.append(styleElement.clone(true)); + }); + if (_viewFuture != null) { + return _viewFuture.then((ViewFactory viewFactory) { + if (shadowScope.isAttached) { + shadowDom.nodes.addAll( + viewFactory(shadowInjector).nodes); + } + return shadowDom; + }); + } + return shadowDom; + })); + + var probe; + var shadowModule = new Module() + ..bindByKey(_ref.typeKey) + ..bindByKey(NG_ELEMENT_KEY) + ..bindByKey(EVENT_HANDLER_KEY, toImplementation: ShadowRootEventHandler) + ..bindByKey(SCOPE_KEY, toValue: shadowScope) + ..bindByKey(TEMPLATE_LOADER_KEY, toValue: templateLoader) + ..bindByKey(SHADOW_ROOT_KEY, toValue: shadowDom); + + if (_f.config.elementProbeEnabled) { + shadowModule.bindByKey(ELEMENT_PROBE_KEY, toFactory: (_) => probe); + } + + shadowInjector = injector.createChild([shadowModule], name: SHADOW_DOM_INJECTOR_NAME); + + if (_f.config.elementProbeEnabled) { + probe = _f.expando[shadowDom] = + new ElementProbe(injector.getByKey(ELEMENT_PROBE_KEY), + shadowDom, shadowInjector, shadowScope); + shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) { + _f.expando[shadowDom] = null; + }); + } + + var controller = shadowInjector.getByKey(_ref.typeKey); + BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); + shadowScope.context[_component.publishAs] = controller; + + return controller; + }; } } diff --git a/lib/core_dom/transcluding_component_factory.dart b/lib/core_dom/transcluding_component_factory.dart index fcd2881c5..53907aa38 100644 --- a/lib/core_dom/transcluding_component_factory.dart +++ b/lib/core_dom/transcluding_component_factory.dart @@ -70,20 +70,44 @@ class ContentPort { @Injectable() class TranscludingComponentFactory implements ComponentFactory { - final Expando _expando; - final CompilerConfig _config; - TranscludingComponentFactory(this._expando, this._config); + final Expando expando; + final ViewCache viewCache; + final CompilerConfig config; - FactoryFn call(dom.Node node, DirectiveRef ref) { + TranscludingComponentFactory(this.expando, this.viewCache, this.config); + + bind(DirectiveRef ref, directives) => + new BoundTranscludingComponentFactory(this, ref, directives); +} + +class BoundTranscludingComponentFactory implements BoundComponentFactory { + final TranscludingComponentFactory _f; + final DirectiveRef _ref; + final DirectiveMap _directives; + + Component get _component => _ref.annotation as Component; + async.Future _viewFuture; + + BoundTranscludingComponentFactory(this._f, this._ref, this._directives) { + _viewFuture = BoundComponentFactory._viewFuture( + _component, + _f.viewCache, + _directives); + } + + FactoryFn call(dom.Node node) { // CSS is not supported. - assert((ref.annotation as Component).cssUrls == null || - (ref.annotation as Component).cssUrls.isEmpty); + assert(_component.cssUrls == null || + _component.cssUrls.isEmpty); var element = node as dom.Element; return (Injector injector) { + var childInjector; - var component = ref.annotation as Component; + var childInjectorCompleter; // Used if the ViewFuture is available before the childInjector. + + var component = _component; Scope scope = injector.getByKey(SCOPE_KEY); ViewCache viewCache = injector.getByKey(VIEW_CACHE_KEY); Http http = injector.getByKey(HTTP_KEY); @@ -94,14 +118,21 @@ class TranscludingComponentFactory implements ComponentFactory { var contentPort = new ContentPort(element); // Append the component's template as children - var viewFuture = ComponentFactory._viewFuture(component, viewCache, directives); var elementFuture; - if (viewFuture != null) { - elementFuture = viewFuture.then((ViewFactory viewFactory) { + if (_viewFuture != null) { + elementFuture = _viewFuture.then((ViewFactory viewFactory) { contentPort.pullNodes(); - element.nodes.addAll(viewFactory(childInjector).nodes); - return element; + if (childInjector != null) { + element.nodes.addAll(viewFactory(childInjector).nodes); + return element; + } else { + childInjectorCompleter = new async.Completer(); + return childInjectorCompleter.future.then((childInjector) { + element.nodes.addAll(viewFactory(childInjector).nodes); + return element; + }); + } }); } else { elementFuture = new async.Future.microtask(() => contentPort.pullNodes()); @@ -112,21 +143,24 @@ class TranscludingComponentFactory implements ComponentFactory { var probe; var childModule = new Module() - ..bind(ref.type) + ..bind(_ref.type) ..bind(NgElement) ..bind(ContentPort, toValue: contentPort) ..bind(Scope, toValue: shadowScope) ..bind(TemplateLoader, toValue: templateLoader) ..bind(dom.ShadowRoot, toValue: new ShadowlessShadowRoot(element)); - if (_config.elementProbeEnabled) { + if (_f.config.elementProbeEnabled) { childModule.bind(ElementProbe, toFactory: (_) => probe); } childInjector = injector.createChild([childModule], name: SHADOW_DOM_INJECTOR_NAME); + if (childInjectorCompleter != null) { + childInjectorCompleter.complete(childInjector); + } - var controller = childInjector.get(ref.type); + var controller = childInjector.get(_ref.type); shadowScope.context[component.publishAs] = controller; - ComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); + BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); return controller; }; } diff --git a/test/core_dom/element_binder_builder_spec.dart b/test/core_dom/element_binder_builder_spec.dart index 12c7033b8..72a45eb8e 100644 --- a/test/core_dom/element_binder_builder_spec.dart +++ b/test/core_dom/element_binder_builder_spec.dart @@ -32,7 +32,7 @@ main() => describe('ElementBinderBuilder', () { beforeEach((DirectiveMap d, ElementBinderFactory f) { directives = d; - b = f.builder(null); + b = f.builder(null, null); }); addDirective(selector) { @@ -49,17 +49,17 @@ main() => describe('ElementBinderBuilder', () { addDirective('[directive]'); expect(b.decorators.length).toEqual(1); - expect(b.component).toBeNull(); + expect(b.componentData).toBeNull(); expect(b.childMode).toEqual(Directive.COMPILE_CHILDREN); }); - it('should add a component', () { + it('should add a component', async(() { addDirective('component'); expect(b.decorators.length).toEqual(0); - expect(b.component).toBeNotNull(); - }); + expect(b.componentData).toBeNotNull(); + })); it('should add a template', () { addDirective('[structural]'); @@ -71,7 +71,7 @@ main() => describe('ElementBinderBuilder', () { addDirective('[ignore-children]'); expect(b.decorators.length).toEqual(1); - expect(b.component).toBeNull(); + expect(b.componentData).toBeNull(); expect(b.childMode).toEqual(Directive.IGNORE_CHILDREN); }); }); diff --git a/test/core_dom/selector_spec.dart b/test/core_dom/selector_spec.dart index 9d63d516d..35c8785a9 100644 --- a/test/core_dom/selector_spec.dart +++ b/test/core_dom/selector_spec.dart @@ -138,7 +138,7 @@ main() { ])); }); - it('should sort by priority', () { + it('should sort by priority', async(() { TemplateElementBinder eb = selector(element = e( '')); expect(eb, @@ -154,7 +154,7 @@ main() { ], component: { "selector": "component", "value": null, "element": element })); - }); + })); it('should match on multiple directives', () { expect(selector(element = e('
')), @@ -275,7 +275,7 @@ class DirectiveInfosMatcher extends Matcher { pass = pass && _refMatches((binder as TemplateElementBinder).template, expectedTemplate); } if (pass && expectedComponent != null) { - pass = pass && _refMatches(binder.component, expectedComponent); + pass = pass && _refMatches(binder.componentData.ref, expectedComponent); } return pass; } diff --git a/test/directive/ng_base_css_spec.dart b/test/directive/ng_base_css_spec.dart index 5a6bde427..bc56214be 100644 --- a/test/directive/ng_base_css_spec.dart +++ b/test/directive/ng_base_css_spec.dart @@ -25,9 +25,9 @@ main() => describe('NgBaseCss', () { it('should load css urls from ng-base-css', async((TestBed _, MockHttpBackend backend) { backend - ..expectGET('base.css').respond(200, '.base{}') ..expectGET('simple.css').respond(200, '.simple{}') - ..expectGET('simple.html').respond(200, '
Simple!
'); + ..expectGET('simple.html').respond(200, '
Simple!
') + ..expectGET('base.css').respond(200, '.base{}'); var element = e('
ignore
'); _.compile(element); @@ -43,9 +43,9 @@ main() => describe('NgBaseCss', () { it('ng-base-css should overwrite parent ng-base-csses', async((TestBed _, MockHttpBackend backend) { backend - ..expectGET('base.css').respond(200, '.base{}') ..expectGET('simple.css').respond(200, '.simple{}') - ..expectGET('simple.html').respond(200, '
Simple!
'); + ..expectGET('simple.html').respond(200, '
Simple!
') + ..expectGET('base.css').respond(200, '.base{}'); var element = e('
ignore
'); _.compile(element); @@ -83,9 +83,9 @@ main() => describe('NgBaseCss', () { it('ng-base-css should be available from the injector', async((TestBed _, MockHttpBackend backend) { backend - ..expectGET('injected.css').respond(200, '.injected{}') ..expectGET('simple.css').respond(200, '.simple{}') - ..expectGET('simple.html').respond(200, '
Simple!
'); + ..expectGET('simple.html').respond(200, '
Simple!
') + ..expectGET('injected.css').respond(200, '.injected{}'); var element = e('
ignore
'); _.compile(element);