From ca958464c45d9399ff41a2aafb5ae9ea7d8f23cf Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Tue, 7 Apr 2015 17:24:09 -0700 Subject: [PATCH] refactor(render): create and store render ProtoViewRef in every app ProtoView Needed to change Renderer.mergeChildComponentProtoViews to not create new ProtoViews to be able to deal with cyclic references. This commit is part of using the new render layer in Angular. --- modules/angular2/src/core/application.js | 12 +- .../angular2/src/core/compiler/compiler.js | 124 ++++++----- .../src/core/compiler/proto_view_factory.js | 26 +-- modules/angular2/src/core/compiler/view.js | 26 +-- modules/angular2/src/render/api.js | 10 +- .../src/render/dom/direct_dom_renderer.js | 24 +-- .../src/render/dom/view/element_binder.js | 24 --- .../src/render/dom/view/proto_view.js | 21 +- .../test/core/compiler/compiler_spec.js | 119 ++++++++--- .../core/compiler/element_injector_spec.js | 2 +- .../core/compiler/shadow_dom_strategy_spec.js | 6 +- .../test/core/compiler/view_container_spec.js | 4 +- .../angular2/test/core/compiler/view_spec.js | 94 +++------ .../direct_dom_renderer_integration_spec.js | 132 ++++++++---- .../test/render/dom/integration_testbed.js | 124 ++++------- .../shadow_dom_emulation_integration_spec.js | 197 +++++++++++------- 16 files changed, 509 insertions(+), 436 deletions(-) diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index fd668ced1e..176689c50f 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -3,7 +3,6 @@ import {Type, isBlank, isPresent, BaseException, assertionsEnabled, print, strin import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter'; import {Compiler, CompilerCache} from './compiler/compiler'; -import {ProtoView} from './compiler/view'; import {Reflector, reflector} from 'angular2/src/reflection/reflection'; import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'angular2/change_detection'; import {ExceptionHandler} from './exception_handler'; @@ -72,12 +71,11 @@ function _injectorBindings(appComponentType): List { throw new BaseException(`Only Components can be bootstrapped; ` + `Directive of ${stringify(type)} is not a Component`); } - return compiler.compile(appComponentAnnotatedType.type).then( - (protoView) => { - var appProtoView = ProtoView.createRootProtoView(protoView, appElement, - DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation), - changeDetection.createProtoChangeDetector('root'), - strategy); + return compiler.compileRoot( + appElement, + DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation) + ).then( + (appProtoView) => { // The light Dom of the app element is not considered part of // the angular application. Thus the context and lightDomInjector are // empty. diff --git a/modules/angular2/src/core/compiler/compiler.js b/modules/angular2/src/core/compiler/compiler.js index 19661492dd..6c382412e9 100644 --- a/modules/angular2/src/core/compiler/compiler.js +++ b/modules/angular2/src/core/compiler/compiler.js @@ -85,6 +85,17 @@ export class NewCompiler { return DirectiveBinding.createFromType(meta.type, meta.annotation); } + // Create a rootView as if the compiler encountered . + // Used for bootstrapping. + compileRoot(elementOrSelector, componentBinding:DirectiveBinding):Promise { + return this._renderer.createRootProtoView(elementOrSelector, 'root').then( (rootRenderPv) => { + return this._compileNestedProtoViews(null, rootRenderPv, [componentBinding], true) + }).then( (rootProtoView) => { + rootProtoView.instantiateInPlace = true; + return rootProtoView; + }); + } + compile(component: Type):Promise { var protoView = this._compile(this._bindDirective(component)); return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView); @@ -96,7 +107,8 @@ export class NewCompiler { var protoView = this._compilerCache.get(component); if (isPresent(protoView)) { // The component has already been compiled into a ProtoView, - // returns a resolved Promise. + // returns a plain ProtoView, not wrapped inside of a Promise. + // Needed for recursive components. return protoView; } @@ -113,31 +125,72 @@ export class NewCompiler { this._flattenDirectives(template), (directive) => this._bindDirective(directive) ); - - pvPromise = this._compileNoRecurse(componentBinding, template, directives).then( (protoView) => { - // Populate the cache before compiling the nested components, - // so that components can reference themselves in their template. - this._compilerCache.set(component, protoView); - MapWrapper.delete(this._compiling, component); - - // Compile all the components from the template - var nestedPVPromises = this._compileNestedComponents(protoView); - if (nestedPVPromises.length > 0) { - // Returns ProtoView Promise when there are any asynchronous nested ProtoViews. - // The promise will resolved after nested ProtoViews are compiled. - return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises), - (_) => protoView, - (e) => { throw new BaseException(`${e} -> Failed to compile ${stringify(component)}`); } - ); - } - return protoView; + var renderTemplate = this._buildRenderTemplate(component, template, directives); + pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => { + return this._compileNestedProtoViews(componentBinding, renderPv, directives, true); }); + MapWrapper.set(this._compiling, component, pvPromise); return pvPromise; } - _compileNoRecurse(componentBinding, template, directives):Promise { - var component = componentBinding.key.token; + // TODO(tbosch): union type return ProtoView or Promise + _compileNestedProtoViews(componentBinding, renderPv, directives, isComponentRootView) { + var nestedPVPromises = []; + var protoView = this._protoViewFactory.createProtoView(componentBinding, renderPv, directives); + if (isComponentRootView && isPresent(componentBinding)) { + // Populate the cache before compiling the nested components, + // so that components can reference themselves in their template. + var component = componentBinding.key.token; + this._compilerCache.set(component, protoView); + MapWrapper.delete(this._compiling, component); + } + + var binderIndex = 0; + ListWrapper.forEach(protoView.elementBinders, (elementBinder) => { + var nestedComponent = elementBinder.componentDirective; + var nestedRenderProtoView = renderPv.elementBinders[binderIndex].nestedProtoView; + var elementBinderDone = (nestedPv) => { + elementBinder.nestedProtoView = nestedPv; + // Can't set the parentProtoView for components, + // as their ProtoView might be used in multiple other components. + nestedPv.parentProtoView = isPresent(nestedComponent) ? null : protoView; + }; + var nestedCall = null; + if (isPresent(nestedComponent)) { + if (!(nestedComponent.annotation instanceof DynamicComponent)) { + nestedCall = this._compile(nestedComponent); + } + } else if (isPresent(nestedRenderProtoView)) { + nestedCall = this._compileNestedProtoViews(componentBinding, nestedRenderProtoView, directives, false); + } + if (PromiseWrapper.isPromise(nestedCall)) { + ListWrapper.push(nestedPVPromises, nestedCall.then(elementBinderDone)); + } else if (isPresent(nestedCall)) { + elementBinderDone(nestedCall); + } + binderIndex++; + }); + + var protoViewDone = (_) => { + var childComponentRenderPvRefs = []; + ListWrapper.forEach(protoView.elementBinders, (eb) => { + if (isPresent(eb.componentDirective)) { + var componentPv = eb.nestedProtoView; + ListWrapper.push(childComponentRenderPvRefs, isPresent(componentPv) ? componentPv.render : null); + } + }); + this._renderer.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs); + return protoView; + }; + if (nestedPVPromises.length > 0) { + return PromiseWrapper.all(nestedPVPromises).then(protoViewDone); + } else { + return protoViewDone(null); + } + } + + _buildRenderTemplate(component, template, directives) { var componentUrl = this._urlResolver.resolve( this._appUrl, this._componentUrlMapper.getUrl(component) ); @@ -150,37 +203,12 @@ export class NewCompiler { // is able to resolve urls in stylesheets. templateAbsUrl = componentUrl; } - var renderTemplate = new renderApi.Template({ + return new renderApi.Template({ componentId: stringify(component), absUrl: templateAbsUrl, inline: template.inline, directives: ListWrapper.map(directives, this._buildRenderDirective) }); - return this._renderer.compile(renderTemplate).then( (renderPv) => { - return this._protoViewFactory.createProtoView(componentBinding.annotation, renderPv, directives); - }); - } - - _compileNestedComponents(protoView, nestedPVPromises = null):List { - if (isBlank(nestedPVPromises)) { - nestedPVPromises = []; - } - ListWrapper.map(protoView.elementBinders, (elementBinder) => { - var nestedComponent = elementBinder.componentDirective; - if (isPresent(nestedComponent) && !(nestedComponent.annotation instanceof DynamicComponent)) { - var nestedCall = this._compile(nestedComponent); - if (PromiseWrapper.isPromise(nestedCall)) { - ListWrapper.push(nestedPVPromises, nestedCall.then( (nestedPv) => { - elementBinder.nestedProtoView = nestedPv; - })); - } else { - elementBinder.nestedProtoView = nestedCall; - } - } else if (isPresent(elementBinder.nestedProtoView)) { - this._compileNestedComponents(elementBinder.nestedProtoView, nestedPVPromises); - } - }); - return nestedPVPromises; } _buildRenderDirective(directiveBinding) { @@ -269,7 +297,7 @@ export class Compiler extends NewCompiler { new DefaultStepFactory(parser, shadowDomStrategy.render), templateLoader ), - null, null + null, shadowDomStrategy.render ), new ProtoViewFactory(changeDetection, shadowDomStrategy) ); diff --git a/modules/angular2/src/core/compiler/proto_view_factory.js b/modules/angular2/src/core/compiler/proto_view_factory.js index 8fc42d2641..9a843ff7d0 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.js +++ b/modules/angular2/src/core/compiler/proto_view_factory.js @@ -20,16 +20,19 @@ export class ProtoViewFactory { this._shadowDomStrategy = shadowDomStrategy; } - createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List):ProtoView { - return this._createProtoView(null, componentAnnotation, renderProtoView, directives); - } - - _createProtoView(parent:ProtoView, componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List):ProtoView { - var protoChangeDetector = this._changeDetection.createProtoChangeDetector('dummy', - componentAnnotation.changeDetection); + createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List):ProtoView { + var protoChangeDetector; + if (isBlank(componentBinding)) { + protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null); + } else { + var componentAnnotation:Component = componentBinding.annotation; + protoChangeDetector = this._changeDetection.createProtoChangeDetector( + 'dummy', componentAnnotation.changeDetection + ); + } var domProtoView = this._getDomProtoView(renderProtoView.render); - var protoView = new ProtoView(domProtoView.element, protoChangeDetector, - this._shadowDomStrategy, parent); + var protoView = new ProtoView(renderProtoView.render, domProtoView.element, protoChangeDetector, + this._shadowDomStrategy, null); for (var i=0; i { protoView.bindVariable(varName, mappedName); diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index 87cdc1dde9..436d0e1941 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -16,6 +16,7 @@ import {Content} from './shadow_dom_emulation/content_tag'; import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ViewPool} from './view_pool'; import {EventManager} from 'angular2/src/render/dom/events/event_manager'; +import * as renderApi from 'angular2/src/render/api'; const NG_BINDING_CLASS = 'ng-binding'; const NG_BINDING_CLASS_SELECTOR = '.ng-binding'; @@ -283,11 +284,14 @@ export class ProtoView { _directiveMementosMap:Map; _directiveMementos:List; + render:renderApi.ProtoViewRef; constructor( + render:renderApi.ProtoViewRef, template, protoChangeDetector:ProtoChangeDetector, shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) { + this.render = render; this.element = template; this.elementBinders = []; this.variableBindings = MapWrapper.create(); @@ -642,28 +646,6 @@ export class ProtoView { return MapWrapper.get(this._directiveMementosMap, id); } - - // Create a rootView as if the compiler encountered , - // and the component template is already compiled into protoView. - // Used for bootstrapping. - static createRootProtoView(protoView: ProtoView, - insertionElement, - rootComponentBinding: DirectiveBinding, - protoChangeDetector:ProtoChangeDetector, - shadowDomStrategy: ShadowDomStrategy - ): ProtoView { - - DOM.addClass(insertionElement, NG_BINDING_CLASS); - var cmpType = rootComponentBinding.key.token; - var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy); - rootProtoView.instantiateInPlace = true; - var binder = rootProtoView.bindElement(null, 0, - new ProtoElementInjector(null, 0, [cmpType], true)); - binder.componentDirective = rootComponentBinding; - binder.nestedProtoView = protoView; - shadowDomStrategy.shimAppElement(cmpType, insertionElement); - return rootProtoView; - } } /** diff --git a/modules/angular2/src/render/api.js b/modules/angular2/src/render/api.js index 398aa52d1d..2315bb5dcb 100644 --- a/modules/angular2/src/render/api.js +++ b/modules/angular2/src/render/api.js @@ -141,20 +141,20 @@ export class Renderer { compile(template:Template):Promise { return null; } /** - * Creates a new ProtoView with preset nested components, + * Sets the preset nested components, * which will be instantiated when this protoView is instantiated. + * Note: We can't create new ProtoViewRefs here as we need to support cycles / recursive components. * @param {List} protoViewRefs * ProtoView for every element with a component in this protoView or in a view container's protoView - * @return {List} - * new ProtoViewRef for the given protoView and all of its view container's protoViews */ - mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, protoViewRefs:List):List { return null; } + mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, componentProtoViewRefs:List) { return null; } /** * Creats a ProtoView that will create a root view for the given element, * i.e. it will not clone the element but only attach other proto views to it. + * Contains a single nested component with the given componentId. */ - createRootProtoView(selectorOrElement):ProtoViewRef { return null; } + createRootProtoView(selectorOrElement, componentId):Promise { return null; } /** * Creates a view and all of its nested child components. diff --git a/modules/angular2/src/render/dom/direct_dom_renderer.js b/modules/angular2/src/render/dom/direct_dom_renderer.js index 3451a95fb3..fe2dbe8f84 100644 --- a/modules/angular2/src/render/dom/direct_dom_renderer.js +++ b/modules/angular2/src/render/dom/direct_dom_renderer.js @@ -1,4 +1,4 @@ -import {Promise} from 'angular2/src/facade/async'; +import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import {isBlank, isPresent} from 'angular2/src/facade/lang'; @@ -26,10 +26,6 @@ function _wrapView(view:View) { return new _DirectDomViewRef(view); } -function _wrapProtoView(protoView:ProtoView) { - return new DirectDomProtoViewRef(protoView); -} - function _collectComponentChildViewRefs(view, target = null) { if (isBlank(target)) { target = []; @@ -83,22 +79,22 @@ export class DirectDomRenderer extends api.Renderer { return this._compiler.compile(template); } - mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List):List { - var protoViews = []; + mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List) { _resolveProtoView(protoViewRef).mergeChildComponentProtoViews( - ListWrapper.map(protoViewRefs, _resolveProtoView), - protoViews + ListWrapper.map(protoViewRefs, _resolveProtoView) ); - return ListWrapper.map(protoViews, _wrapProtoView); } - createRootProtoView(selectorOrElement):api.ProtoViewRef { + createRootProtoView(selectorOrElement, componentId):Promise { var element = selectorOrElement; // TODO: select the element if it is not a real element... var rootProtoViewBuilder = new ProtoViewBuilder(element); rootProtoViewBuilder.setIsRootView(true); - rootProtoViewBuilder.bindElement(element, 'root element').setComponentId('root'); - this._shadowDomStrategy.processElement(null, 'root', element); - return rootProtoViewBuilder.build().render; + var elBinder = rootProtoViewBuilder.bindElement(element, 'root element'); + elBinder.setComponentId(componentId); + elBinder.bindDirective(0); + + this._shadowDomStrategy.processElement(null, componentId, element); + return PromiseWrapper.resolve(rootProtoViewBuilder.build()); } createView(protoViewRef:api.ProtoViewRef):List { diff --git a/modules/angular2/src/render/dom/view/element_binder.js b/modules/angular2/src/render/dom/view/element_binder.js index 9d11c6d2ce..5e1c445e75 100644 --- a/modules/angular2/src/render/dom/view/element_binder.js +++ b/modules/angular2/src/render/dom/view/element_binder.js @@ -1,12 +1,8 @@ import {AST} from 'angular2/change_detection'; import {SetterFn} from 'angular2/src/reflection/types'; -import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import * as protoViewModule from './proto_view'; -/** - * Note: Code that uses this class assumes that is immutable! - */ export class ElementBinder { contentTagSelector: string; textNodeIndices: List; @@ -39,24 +35,4 @@ export class ElementBinder { this.distanceToParent = distanceToParent; this.propertySetters = propertySetters; } - - mergeChildComponentProtoViews(protoViews:List, target:List):ElementBinder { - var nestedProtoView; - if (isPresent(this.componentId)) { - nestedProtoView = ListWrapper.removeAt(protoViews, 0); - } else if (isPresent(this.nestedProtoView)) { - nestedProtoView = this.nestedProtoView.mergeChildComponentProtoViews(protoViews, target); - } - return new ElementBinder({ - parentIndex: this.parentIndex, - textNodeIndices: this.textNodeIndices, - contentTagSelector: this.contentTagSelector, - nestedProtoView: nestedProtoView, - componentId: this.componentId, - eventLocals: this.eventLocals, - eventNames: this.eventNames, - distanceToParent: this.distanceToParent, - propertySetters: this.propertySetters - }); - } } diff --git a/modules/angular2/src/render/dom/view/proto_view.js b/modules/angular2/src/render/dom/view/proto_view.js index 0ffb08231a..95ddd36ab3 100644 --- a/modules/angular2/src/render/dom/view/proto_view.js +++ b/modules/angular2/src/render/dom/view/proto_view.js @@ -6,9 +6,6 @@ import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection import {ElementBinder} from './element_binder'; import {NG_BINDING_CLASS} from '../util'; -/** - * Note: Code that uses this class assumes that is immutable! - */ export class ProtoView { element; elementBinders:List; @@ -28,22 +25,14 @@ export class ProtoView { this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0; } - mergeChildComponentProtoViews(protoViews:List, target:List):ProtoView { - var elementBinders = ListWrapper.createFixedSize(this.elementBinders.length); + mergeChildComponentProtoViews(componentProtoViews:List) { + var componentProtoViewIndex = 0; for (var i=0; i { + it('should pass the component binding', inject([AsyncTestCompleter], (async) => { tplResolver.setTemplate(MainComponent, new Template({inline: '
'})); var compiler = createCompiler([createRenderProtoView()], [createProtoView()]); compiler.compile(MainComponent).then( (protoView) => { var request = protoViewFactory.requests[0]; - expect(request[0]).toEqual(new Component({ - selector: 'main-comp' - })); + expect(request[0].key.token).toBe(MainComponent); async.done(); }); })); @@ -255,7 +253,7 @@ export function main() { }); - it('should load nested components in root ProtoView', inject([AsyncTestCompleter], (async) => { + it('should load nested components', inject([AsyncTestCompleter], (async) => { tplResolver.setTemplate(MainComponent, new Template({inline: '
'})); tplResolver.setTemplate(NestedComponent, new Template({inline: '
'})); var mainProtoView = createProtoView([ @@ -263,34 +261,54 @@ export function main() { ]); var nestedProtoView = createProtoView(); var compiler = createCompiler( - [createRenderProtoView(), createRenderProtoView()], + [ + createRenderProtoView([createRenderComponentElementBinder(0)]), + createRenderProtoView() + ], [mainProtoView, nestedProtoView] ); compiler.compile(MainComponent).then( (protoView) => { expect(protoView).toBe(mainProtoView); expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView); + // parentProtoView of nested components has to be null as components can + // be used by multiple other components. + expect(nestedProtoView.parentProtoView).toBe(null); async.done(); }); })); - it('should load nested components in viewport ProtoView', inject([AsyncTestCompleter], (async) => { + it('should load nested components in viewport', inject([AsyncTestCompleter], (async) => { tplResolver.setTemplate(MainComponent, new Template({inline: '
'})); tplResolver.setTemplate(NestedComponent, new Template({inline: '
'})); var mainProtoView = createProtoView([ - createViewportElementBinder(createProtoView([ - createComponentElementBinder(reader, NestedComponent) - ])) + createViewportElementBinder(null) + ]); + var viewportProtoView = createProtoView([ + createComponentElementBinder(reader, NestedComponent) ]); var nestedProtoView = createProtoView(); var compiler = createCompiler( - [createRenderProtoView(), createRenderProtoView()], - [mainProtoView, nestedProtoView] + [ + createRenderProtoView([ + createRenderViewportElementBinder( + createRenderProtoView([ + createRenderComponentElementBinder(0) + ]) + ) + ]), + createRenderProtoView() + ], + [mainProtoView, viewportProtoView, nestedProtoView] ); compiler.compile(MainComponent).then( (protoView) => { expect(protoView).toBe(mainProtoView); - expect( - mainProtoView.elementBinders[0].nestedProtoView.elementBinders[0].nestedProtoView - ).toBe(nestedProtoView); + expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(viewportProtoView); + expect(viewportProtoView.parentProtoView).toBe(mainProtoView); + expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView); + // parentProtoView of nested components has to be null as components can + // be used by multiple other components. + expect(nestedProtoView.parentProtoView).toBe(null); + async.done(); }); })); @@ -331,7 +349,9 @@ export function main() { createComponentElementBinder(reader, MainComponent) ]); var compiler = createCompiler( - [createRenderProtoView()], + [createRenderProtoView([ + createRenderComponentElementBinder(0) + ])], [mainProtoView] ); compiler.compile(MainComponent).then( (protoView) => { @@ -340,20 +360,44 @@ export function main() { async.done(); }); })); + + it('should create root proto views', inject([AsyncTestCompleter], (async) => { + tplResolver.setTemplate(MainComponent, new Template({inline: '
'})); + var rootProtoView = createProtoView([ + createComponentElementBinder(reader, MainComponent) + ]); + var mainProtoView = createProtoView(); + var compiler = createCompiler( + [ + createRenderProtoView() + ], + [rootProtoView, mainProtoView] + ); + compiler.compileRoot(null, createDirectiveBinding(reader, MainComponent)).then( (protoView) => { + expect(protoView).toBe(rootProtoView); + expect(rootProtoView.elementBinders[0].nestedProtoView).toBe(mainProtoView); + async.done(); + }); + })); }); } +function createDirectiveBinding(reader, type) { + var meta = reader.read(type); + return DirectiveBinding.createFromType(meta.type, meta.annotation); +} + function createProtoView(elementBinders = null) { - var pv = new ProtoView(null, null, null, null); - if (isPresent(elementBinders)) { - pv.elementBinders = elementBinders; + var pv = new ProtoView(null, null, null, null, null); + if (isBlank(elementBinders)) { + elementBinders = []; } + pv.elementBinders = elementBinders; return pv; } function createComponentElementBinder(reader, type) { - var meta = reader.read(type); - var binding = DirectiveBinding.createFromType(meta.type, meta.annotation); + var binding = createDirectiveBinding(reader, type); return new ElementBinder( 0, null, 0, null, binding, @@ -371,8 +415,27 @@ function createViewportElementBinder(nestedProtoView) { return elBinder; } -function createRenderProtoView() { - return new renderApi.ProtoView(); +function createRenderProtoView(elementBinders = null) { + if (isBlank(elementBinders)) { + elementBinders = []; + } + return new renderApi.ProtoView({ + elementBinders: elementBinders + }); +} + +function createRenderComponentElementBinder(directiveIndex) { + return new renderApi.ElementBinder({ + directives: [new renderApi.DirectiveBinder({ + directiveIndex: directiveIndex + })] + }); +} + +function createRenderViewportElementBinder(nestedProtoView) { + return new renderApi.ElementBinder({ + nestedProtoView: nestedProtoView + }); } @Component({ @@ -433,6 +496,12 @@ class FakeRenderer extends renderApi.Renderer { ListWrapper.push(this.requests, template); return PromiseWrapper.resolve(ListWrapper.removeAt(this._results, 0)); } + + createRootProtoView(elementOrSelector, componentId):Promise { + return PromiseWrapper.resolve( + createRenderProtoView([createRenderComponentElementBinder(0)]) + ); + } } class FakeUrlResolver extends UrlResolver { @@ -481,8 +550,8 @@ class FakeProtoViewFactory extends ProtoViewFactory { this._results = results; } - createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List):ProtoView { - ListWrapper.push(this.requests, [componentAnnotation, renderProtoView, directives]); + createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List):ProtoView { + ListWrapper.push(this.requests, [componentBinding, renderProtoView, directives]); return ListWrapper.removeAt(this._results, 0); } } \ No newline at end of file diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index 9998ef71ac..30378450e6 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -577,7 +577,7 @@ export function main() { function createpreBuildObject(eventName, eventHandler) { var handlers = StringMapWrapper.create(); StringMapWrapper.set(handlers, eventName, eventHandler); - var pv = new ProtoView(null, null, null); + var pv = new ProtoView(null, null, null, null); pv.eventHandlers = [handlers]; var view = new View(pv, null, MapWrapper.create()); return new PreBuiltObjects(view, null, null, null); diff --git a/modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js b/modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js index 8c6d6d03b2..7638a58d45 100644 --- a/modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js +++ b/modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js @@ -43,7 +43,7 @@ export function main() { it('should attach the view nodes to the shadow root', () => { var host = el('
'); var nodes = el('
view
'); - var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null); + var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null); var view = pv.instantiate(null, null); strategy.attachTemplate(host, view); @@ -68,7 +68,7 @@ export function main() { it('should attach the view nodes as child of the host element', () => { var host = el('
original content
'); var nodes = el('
view
'); - var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null); + var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null); var view = pv.instantiate(null, null); strategy.attachTemplate(host, view); @@ -92,7 +92,7 @@ export function main() { it('should attach the view nodes as child of the host element', () => { var host = el('
original content
'); var nodes = el('
view
'); - var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null); + var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null); var view = pv.instantiate(null, null); strategy.attachTemplate(host, view); diff --git a/modules/angular2/test/core/compiler/view_container_spec.js b/modules/angular2/test/core/compiler/view_container_spec.js index dc0f3231a5..ee8b88980c 100644 --- a/modules/angular2/test/core/compiler/view_container_spec.js +++ b/modules/angular2/test/core/compiler/view_container_spec.js @@ -71,7 +71,7 @@ export function main() { dom = el(`
`); var insertionElement = dom.childNodes[1]; parentView = createView([dom.childNodes[0]]); - protoView = new ProtoView(el('
hi
'), new DynamicProtoChangeDetector(null, null), + protoView = new ProtoView(null, el('
hi
'), new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)); elementInjector = new ElementInjector(null, null, null); viewContainer = new ViewContainer(parentView, insertionElement, protoView, elementInjector, @@ -216,7 +216,7 @@ export function main() { var parser = new Parser(new Lexer()); viewContainer.hydrate(new Injector([]), null, null); - var pv = new ProtoView(el('
{{}}
'), + var pv = new ProtoView(null, el('
{{}}
'), new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)); pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindTextNode(0, parser.parseBinding('foo', null)); diff --git a/modules/angular2/test/core/compiler/view_spec.js b/modules/angular2/test/core/compiler/view_spec.js index 173e226cb1..b8c523fa66 100644 --- a/modules/angular2/test/core/compiler/view_spec.js +++ b/modules/angular2/test/core/compiler/view_spec.js @@ -63,7 +63,7 @@ export function main() { describe('instantiated from protoView', () => { var view; beforeEach(() => { - var pv = new ProtoView(el('
'), new DynamicProtoChangeDetector(null, null), null); + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); view = pv.instantiate(null, null); }); @@ -90,7 +90,7 @@ export function main() { }); it('should use the view pool to reuse views', () => { - var pv = new ProtoView(el('
'), new DynamicProtoChangeDetector(null, null), null); + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); var fakeView = new FakeView(); pv.returnToPool(fakeView); @@ -101,7 +101,7 @@ export function main() { describe('with locals', function() { var view; beforeEach(() => { - var pv = new ProtoView(el('
'), new DynamicProtoChangeDetector(null, null), null); + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindVariable('context-foo', 'template-foo'); view = createView(pv); }); @@ -137,7 +137,7 @@ export function main() { } it('should collect the root node in the ProtoView element', () => { - var pv = new ProtoView(templateAwareCreateElement('
'), + var pv = new ProtoView(null, templateAwareCreateElement('
'), new DynamicProtoChangeDetector(null, null), null); var view = pv.instantiate(null, null); view.hydrate(null, null, null, null, null); @@ -148,7 +148,7 @@ export function main() { describe('collect elements with property bindings', () => { it('should collect property bindings on the root element if it has the ng-binding class', () => { - var pv = new ProtoView(templateAwareCreateElement('
'), + var pv = new ProtoView(null, templateAwareCreateElement('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, null); pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop')); @@ -160,7 +160,7 @@ export function main() { }); it('should collect property bindings on child elements with ng-binding class', () => { - var pv = new ProtoView(templateAwareCreateElement('
'), + var pv = new ProtoView(null, templateAwareCreateElement('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, null); pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a')); @@ -176,7 +176,7 @@ export function main() { describe('collect text nodes with bindings', () => { it('should collect text nodes under the root element', () => { - var pv = new ProtoView(templateAwareCreateElement('
{{}}{{}}
'), + var pv = new ProtoView(null, templateAwareCreateElement('
{{}}{{}}
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, null); pv.bindTextNode(0, parser.parseBinding('a', null)); @@ -190,7 +190,7 @@ export function main() { }); it('should collect text nodes with bindings on child elements with ng-binding class', () => { - var pv = new ProtoView(templateAwareCreateElement('
{{}}
'), + var pv = new ProtoView(null, templateAwareCreateElement('
{{}}
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, null); pv.bindTextNode(0, parser.parseBinding('b', null)); @@ -207,7 +207,7 @@ export function main() { describe('inplace instantiation', () => { it('should be supported.', () => { var template = el('
'); - var pv = new ProtoView(template, new DynamicProtoChangeDetector(null, null), + var pv = new ProtoView(null, template, new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)); pv.instantiateInPlace = true; var view = pv.instantiate(null, null); @@ -217,7 +217,7 @@ export function main() { it('should be off by default.', () => { var template = el('
') - var pv = new ProtoView(template, new DynamicProtoChangeDetector(null, null), + var pv = new ProtoView(null, template, new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)) var view = pv.instantiate(null, null); view.hydrate(null, null, null, null, null); @@ -235,7 +235,7 @@ export function main() { describe('create ElementInjectors', () => { it('should use the directives of the ProtoElementInjector', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective])); @@ -246,7 +246,7 @@ export function main() { }); it('should use the correct parent', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); pv.bindElement(null, 0, protoParent); @@ -260,7 +260,7 @@ export function main() { }); it('should not pass the host injector when a parent injector exists', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); pv.bindElement(null, 0, protoParent); @@ -276,7 +276,7 @@ export function main() { }); it('should pass the host injector when there is no parent injector', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirective])); var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]); @@ -293,7 +293,7 @@ export function main() { describe('collect root element injectors', () => { it('should collect a single root element injector', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); pv.bindElement(null, 0, protoParent); @@ -306,7 +306,7 @@ export function main() { }); it('should collect multiple root element injectors', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(null, 0, new ProtoElementInjector(null, 2, [AnotherDirective])); @@ -324,7 +324,7 @@ export function main() { var ctx; function createComponentWithSubPV(subProtoView) { - var pv = new ProtoView(el(''), + var pv = new ProtoView(null, el(''), new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)); var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeComponent], true)); binder.componentDirective = someComponentDirective; @@ -340,7 +340,7 @@ export function main() { } it('should expose component services to the component', () => { - var subpv = new ProtoView(el(''), new DynamicProtoChangeDetector(null, null), null); + var subpv = new ProtoView(null, el(''), new DynamicProtoChangeDetector(null, null), null); var pv = createComponentWithSubPV(subpv); var view = createNestedView(pv); @@ -351,7 +351,7 @@ export function main() { it('should expose component services and component instance to directives in the shadow Dom', () => { - var subpv = new ProtoView( + var subpv = new ProtoView(null, el('
hello shadow dom
'), new DynamicProtoChangeDetector(null, null), null); @@ -376,7 +376,7 @@ export function main() { } it('dehydration should dehydrate child component views too', () => { - var subpv = new ProtoView( + var subpv = new ProtoView(null, el('
hello shadow dom
'), new DynamicProtoChangeDetector(null, null), null); @@ -394,7 +394,7 @@ export function main() { }); it('should create shadow dom (Native Strategy)', () => { - var subpv = new ProtoView(el('hello shadow dom'), + var subpv = new ProtoView(null, el('hello shadow dom'), new DynamicProtoChangeDetector(null, null), null); var pv = createComponentWithSubPV(subpv); @@ -405,10 +405,10 @@ export function main() { }); it('should emulate shadow dom (Emulated Strategy)', () => { - var subpv = new ProtoView(el('hello shadow dom'), + var subpv = new ProtoView(null, el('hello shadow dom'), new DynamicProtoChangeDetector(null, null), null); - var pv = new ProtoView(el(''), + var pv = new ProtoView(null, el(''), new DynamicProtoChangeDetector(null, null), new EmulatedScopedShadowDomStrategy(null, null, null)); var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeComponent], true)); binder.componentDirective = readDirectiveBinding(SomeComponent); @@ -422,9 +422,9 @@ export function main() { describe('with template views', () => { function createViewWithViewport() { - var templateProtoView = new ProtoView( + var templateProtoView = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); - var pv = new ProtoView(el(''), + var pv = new ProtoView(null, el(''), new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)); var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeViewport])); binder.viewportDirective = someViewportDirective; @@ -470,7 +470,7 @@ export function main() { } function createProtoView() { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new TestProtoElementInjector(null, 0, [])); pv.bindEvent('click', parser.parseBinding('callMe($event)', null)); @@ -505,7 +505,7 @@ export function main() { }); it('should support custom event emitters', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new TestProtoElementInjector(null, 0, [EventEmitterDirective])); pv.bindEvent('click', parser.parseBinding('callMe($event)', null)); @@ -526,7 +526,7 @@ export function main() { }); it('should bind to directive events', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirectiveWithEventHandler])); pv.bindEvent('click', parser.parseAction('onEvent($event)', null), 0); @@ -551,7 +551,7 @@ export function main() { } it('should consume text node changes', () => { - var pv = new ProtoView(el('
{{}}
'), + var pv = new ProtoView(null, el('
{{}}
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, null); pv.bindTextNode(0, parser.parseBinding('foo', null)); @@ -563,7 +563,7 @@ export function main() { }); it('should consume element binding changes', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, null); pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id')); @@ -575,7 +575,7 @@ export function main() { }); it('should consume directive watch expression change', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop')); @@ -587,7 +587,7 @@ export function main() { }); it('should notify a directive about changes after all its properties have been set', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [ @@ -607,7 +607,7 @@ export function main() { }); it('should provide a map of updated properties using onChange callback', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [ @@ -634,7 +634,7 @@ export function main() { }); it('should invoke the onAllChangesDone callback', () => { - var pv = new ProtoView(el('
'), + var pv = new ProtoView(null, el('
'), new DynamicProtoChangeDetector(null, null), null); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [ @@ -651,32 +651,6 @@ export function main() { }); }); - describe('protoView createRootProtoView', () => { - var element, pv; - beforeEach(() => { - element = DOM.createElement('div'); - pv = new ProtoView(el('
hi
'), new DynamicProtoChangeDetector(null, null), - new NativeShadowDomStrategy(null)); - }); - - it('should create the root component when instantiated', () => { - var rootProtoView = ProtoView.createRootProtoView(pv, element, - someComponentDirective, new DynamicProtoChangeDetector(null, null), - new NativeShadowDomStrategy(null)); - var view = rootProtoView.instantiate(null, null); - view.hydrate(new Injector([]), null, null, null, null); - expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null); - }); - - it('should inject the protoView into the shadowDom', () => { - var rootProtoView = ProtoView.createRootProtoView(pv, element, - someComponentDirective, new DynamicProtoChangeDetector(null, null), - new NativeShadowDomStrategy(null)); - var view = rootProtoView.instantiate(null, null); - view.hydrate(new Injector([]), null, null, null, null); - expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi'); - }); - }); }); } diff --git a/modules/angular2/test/render/dom/direct_dom_renderer_integration_spec.js b/modules/angular2/test/render/dom/direct_dom_renderer_integration_spec.js index a2fd6ba8a2..d7a7dd3da1 100644 --- a/modules/angular2/test/render/dom/direct_dom_renderer_integration_spec.js +++ b/modules/angular2/test/render/dom/direct_dom_renderer_integration_spec.js @@ -21,7 +21,11 @@ import {IntegrationTestbed, LoggingEventDispatcher, FakeEvent} from './integrati export function main() { describe('DirectDomRenderer integration', () => { - var testbed, renderer, rootEl, rootProtoViewRef, eventPlugin, compile; + var testbed, renderer, eventPlugin, compile, rootEl; + + beforeEach(() => { + rootEl = el('
'); + }); function createRenderer({urlData, viewCacheCapacity, shadowDomStrategy, templates}={}) { testbed = new IntegrationTestbed({ @@ -31,54 +35,66 @@ export function main() { templates: templates }); renderer = testbed.renderer; - rootEl = testbed.rootEl; - rootProtoViewRef = testbed.rootProtoViewRef; eventPlugin = testbed.eventPlugin; - compile = (template, directives) => testbed.compile(template, directives); + compile = (rootEl, componentId) => testbed.compile(rootEl, componentId); } - it('should create root views while using the given elements in place', () => { + it('should create root views while using the given elements in place', inject([AsyncTestCompleter], (async) => { createRenderer(); - var viewRefs = renderer.createView(rootProtoViewRef); - expect(viewRefs.length).toBe(1); - expect(viewRefs[0].delegate.rootNodes[0]).toEqual(rootEl); - }); + renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => { + expect(rootProtoView.elementBinders[0].directives[0].directiveIndex).toBe(0); + var viewRefs = renderer.createView(rootProtoView.render); + expect(viewRefs.length).toBe(1); + expect(viewRefs[0].delegate.rootNodes[0]).toEqual(rootEl); + async.done(); + }); + })); it('should add a static component', inject([AsyncTestCompleter], (async) => { createRenderer(); - var template = new Template({ - componentId: 'someComponent', - inline: 'hello', - directives: [] - }); - renderer.compile(template).then( (pv) => { - var mergedProtoViewRefs = renderer.mergeChildComponentProtoViews(rootProtoViewRef, [pv.render]); - renderer.createView(mergedProtoViewRefs[0]); - expect(rootEl).toHaveText('hello'); - async.done(); + renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => { + var template = new Template({ + componentId: 'someComponent', + inline: 'hello', + directives: [] + }); + renderer.compile(template).then( (pv) => { + renderer.mergeChildComponentProtoViews(rootProtoView.render, [pv.render]); + renderer.createView(rootProtoView.render); + expect(rootEl).toHaveText('hello'); + async.done(); + }); }); })); it('should add a a dynamic component', inject([AsyncTestCompleter], (async) => { createRenderer(); - var template = new Template({ - componentId: 'someComponent', - inline: 'hello', - directives: [] - }); - renderer.compile(template).then( (pv) => { - var rootViewRef = renderer.createView(rootProtoViewRef)[0]; - var childComponentViewRef = renderer.createView(pv.render)[0]; - renderer.setDynamicComponentView(rootViewRef, 0, childComponentViewRef); - expect(rootEl).toHaveText('hello'); - async.done(); + renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => { + var template = new Template({ + componentId: 'someComponent', + inline: 'hello', + directives: [] + }); + renderer.compile(template).then( (pv) => { + var rootViewRef = renderer.createView(rootProtoView.render)[0]; + var childComponentViewRef = renderer.createView(pv.render)[0]; + renderer.setDynamicComponentView(rootViewRef, 0, childComponentViewRef); + expect(rootEl).toHaveText('hello'); + async.done(); + }); }); })); it('should update text nodes', inject([AsyncTestCompleter], (async) => { - createRenderer(); - compile('{{a}}', [someComponent]).then( (pvRefs) => { - var viewRefs = renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'someComponent', + inline: '{{a}}', + directives: [] + })] + }); + compile(rootEl, 'someComponent').then( (rootProtoView) => { + var viewRefs = renderer.createView(rootProtoView.render); renderer.setText(viewRefs[1], 0, 'hello'); expect(rootEl).toHaveText('hello'); async.done(); @@ -86,9 +102,15 @@ export function main() { })); it('should update element properties', inject([AsyncTestCompleter], (async) => { - createRenderer(); - compile('', []).then( (pvRefs) => { - var viewRefs = renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'someComponent', + inline: '', + directives: [] + })] + }); + compile(rootEl, 'someComponent').then( (rootProtoView) => { + var viewRefs = renderer.createView(rootProtoView.render); renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello'); expect(DOM.childNodes(rootEl)[0].value).toEqual('hello'); async.done(); @@ -96,10 +118,17 @@ export function main() { })); it('should add and remove views to and from containers', inject([AsyncTestCompleter], (async) => { - createRenderer(); - compile('', []).then( (pvRefs) => { - var viewRef = renderer.createView(pvRefs[0])[1]; - var vcProtoViewRef = pvRefs[2]; + createRenderer({ + templates: [new Template({ + componentId: 'someComponent', + inline: '', + directives: [] + })] + }); + compile(rootEl, 'someComponent').then( (rootProtoView) => { + var viewRef = renderer.createView(rootProtoView.render)[1]; + var vcProtoViewRef = rootProtoView.elementBinders[0] + .nestedProtoView.elementBinders[0].nestedProtoView.render; var vcRef = new ViewContainerRef(viewRef, 0); var childViewRef = renderer.createView(vcProtoViewRef)[0]; @@ -115,10 +144,17 @@ export function main() { it('should cache views', inject([AsyncTestCompleter], (async) => { createRenderer({ + templates: [new Template({ + componentId: 'someComponent', + inline: '', + directives: [] + })], viewCacheCapacity: 2 }); - compile('', []).then( (pvRefs) => { - var vcProtoViewRef = pvRefs[2]; + compile(rootEl, 'someComponent').then( (rootProtoView) => { + var vcProtoViewRef = rootProtoView.elementBinders[0] + .nestedProtoView.elementBinders[0].nestedProtoView.render; + var viewRef1 = renderer.createView(vcProtoViewRef)[0]; renderer.destroyView(viewRef1); var viewRef2 = renderer.createView(vcProtoViewRef)[0]; @@ -133,9 +169,15 @@ export function main() { // TODO(tbosch): This is not working yet as we commented out // the event expression processing... xit('should handle events', inject([AsyncTestCompleter], (async) => { - createRenderer(); - compile('', []).then( (pvRefs) => { - var viewRef = renderer.createView(pvRefs[0])[1]; + createRenderer({ + templates: [new Template({ + componentId: 'someComponent', + inline: '', + directives: [] + })] + }); + compile(rootEl, 'someComponent').then( (rootProtoView) => { + var viewRef = renderer.createView(rootProtoView.render)[1]; var dispatcher = new LoggingEventDispatcher(); renderer.setEventDispatcher(viewRef, dispatcher); var inputEl = DOM.childNodes(rootEl)[0]; diff --git a/modules/angular2/test/render/dom/integration_testbed.js b/modules/angular2/test/render/dom/integration_testbed.js index dd81306e61..eb34d2e042 100644 --- a/modules/angular2/test/render/dom/integration_testbed.js +++ b/modules/angular2/test/render/dom/integration_testbed.js @@ -1,7 +1,3 @@ -import { - el -} from 'angular2/test_lib'; - import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang'; import {MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; @@ -23,11 +19,8 @@ import {ViewFactory} from 'angular2/src/render/dom/view/view_factory'; export class IntegrationTestbed { renderer; parser; - rootEl; - rootProtoViewRef; eventPlugin; _templates:Map; - _compileCache:Map>; constructor({urlData, viewCacheCapacity, shadowDomStrategy, templates}) { this._templates = MapWrapper.create(); @@ -36,7 +29,6 @@ export class IntegrationTestbed { MapWrapper.set(this._templates, template.componentId, template); }); } - this._compileCache = MapWrapper.create(); var parser = new Parser(new Lexer()); var urlResolver = new UrlResolver(); if (isBlank(shadowDomStrategy)) { @@ -54,90 +46,66 @@ export class IntegrationTestbed { var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone()); var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy); this.renderer = new DirectDomRenderer(compiler, viewFactory, shadowDomStrategy); - - this.rootEl = el('
'); - this.rootProtoViewRef = this.renderer.createRootProtoView(this.rootEl); } - compile(templateHtml, directives):Promise> { - return this._compileRecurse(new Template({ - componentId: 'root', - inline: templateHtml, - directives: directives - })).then( (protoViewRefs) => { - return this._flattenList([ - this.renderer.mergeChildComponentProtoViews(this.rootProtoViewRef, [protoViewRefs[0]]), - protoViewRefs + compile(rootEl, componentId):Promise { + return this.renderer.createRootProtoView(rootEl, componentId).then( (rootProtoView) => { + return this._compileNestedProtoViews(rootProtoView, [ + new DirectiveMetadata({ + type: DirectiveMetadata.COMPONENT_TYPE, + id: componentId + }) ]); }); } - _compileRecurse(template):Promise> { - var result = MapWrapper.get(this._compileCache, template.componentId); - if (isPresent(result)) { - return result; - } - result = this.renderer.compile(template).then( (pv) => { - var childComponentPromises = ListWrapper.map( - this._findNestedComponentIds(template, pv), - (componentId) => { - var childTemplate = MapWrapper.get(this._templates, componentId); - if (isBlank(childTemplate)) { - throw new BaseException(`Could not find template for ${componentId}!`); - } - return this._compileRecurse(childTemplate); - } - ); - return PromiseWrapper.all(childComponentPromises).then( - (protoViewRefsWithChildren) => { - var protoViewRefs = - ListWrapper.map(protoViewRefsWithChildren, (arr) => arr[0]); - return this._flattenList([ - this.renderer.mergeChildComponentProtoViews(pv.render, protoViewRefs), - protoViewRefsWithChildren - ]); - } - ); + _compile(template):Promise { + return this.renderer.compile(template).then( (protoView) => { + return this._compileNestedProtoViews(protoView, template.directives); }); - MapWrapper.set(this._compileCache, template.componentId, result); - return result; } - _findNestedComponentIds(template, pv, target = null):List { - if (isBlank(target)) { - target = []; - } - for (var binderIdx=0; binderIdx { - var meta = template.directives[db.directiveIndex]; - if (meta.type === DirectiveMetadata.COMPONENT_TYPE) { - componentDirective = meta; + _compileNestedProtoViews(protoView, directives):Promise { + var childComponentRenderPvRefs = []; + var nestedPVPromises = []; + ListWrapper.forEach(protoView.elementBinders, (elementBinder) => { + var nestedComponentId = null; + ListWrapper.forEach(elementBinder.directives, (db) => { + var directiveMeta = directives[db.directiveIndex]; + if (directiveMeta.type === DirectiveMetadata.COMPONENT_TYPE) { + nestedComponentId = directiveMeta.id; } }); - if (isPresent(componentDirective)) { - ListWrapper.push(target, componentDirective.id); - } else if (isPresent(eb.nestedProtoView)) { - this._findNestedComponentIds(template, eb.nestedProtoView, target); + var nestedCall; + if (isPresent(nestedComponentId)) { + var childTemplate = MapWrapper.get(this._templates, nestedComponentId); + if (isBlank(childTemplate)) { + throw new BaseException(`Could not find template for ${nestedComponentId}!`); + } + nestedCall = this._compile(childTemplate); + } else if (isPresent(elementBinder.nestedProtoView)) { + nestedCall = this._compileNestedProtoViews(elementBinder.nestedProtoView, directives); } - } - return target; - } - - _flattenList(tree:List, out:List = null):List { - if (isBlank(out)) { - out = []; - } - for (var i = 0; i < tree.length; i++) { - var item = tree[i]; - if (ListWrapper.isList(item)) { - this._flattenList(item, out); - } else { - ListWrapper.push(out, item); + if (isPresent(nestedCall)) { + ListWrapper.push( + nestedPVPromises, + nestedCall.then( (nestedPv) => { + elementBinder.nestedProtoView = nestedPv; + if (isPresent(nestedComponentId)) { + ListWrapper.push(childComponentRenderPvRefs, nestedPv.render); + } + }) + ); } + }); + if (nestedPVPromises.length > 0) { + return PromiseWrapper.all(nestedPVPromises).then((_) => { + this.renderer.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs); + return protoView; + }); + } else { + return PromiseWrapper.resolve(protoView); } - return out; } } diff --git a/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.js b/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.js index 8635fe646e..fe57ce4e3a 100644 --- a/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.js @@ -40,34 +40,45 @@ export function main() { StringMapWrapper.set(strategies, "native", () => new NativeShadowDomStrategy(styleUrlResolver)); } + beforeEach( () => { + urlResolver = new UrlResolver(); + styleUrlResolver = new StyleUrlResolver(urlResolver); + styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver); + }); + + StringMapWrapper.forEach(strategies, (strategyFactory, name) => { describe(`${name} shadow dom strategy`, () => { - var testbed, renderer, rootEl, compile, strategy; + var testbed, renderer, rootEl, compile; - beforeEach( () => { - urlResolver = new UrlResolver(); - styleUrlResolver = new StyleUrlResolver(urlResolver); - styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver); - strategy = strategyFactory(); + function createRenderer({templates}) { testbed = new IntegrationTestbed({ - shadowDomStrategy: strategy, - templates: templates + shadowDomStrategy: strategyFactory(), + templates: ListWrapper.concat(templates, componentTemplates) }); renderer = testbed.renderer; - rootEl = testbed.rootEl; - compile = (template, directives) => testbed.compile(template, directives); + compile = (rootEl, componentId) => testbed.compile(rootEl, componentId); + } + + beforeEach( () => { + rootEl = el('
'); }); it('should support simple components', inject([AsyncTestCompleter], (async) => { - var temp = '' + - '
A
' + - '
'; - - compile(temp, [simple]).then( (pvRefs) => { - renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'main', + inline: '' + + '
A
' + + '
', + directives: [simple] + })] + }); + compile(rootEl, 'main').then( (pv) => { + renderer.createView(pv.render); expect(rootEl).toHaveText('SIMPLE(A)'); @@ -76,14 +87,19 @@ export function main() { })); it('should support multiple content tags', inject([AsyncTestCompleter], (async) => { - var temp = '' + - '
B
' + - '
C
' + - '
A
' + - '
'; - - compile(temp, [multipleContentTagsComponent]).then( (pvRefs) => { - renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'main', + inline: '' + + '
B
' + + '
C
' + + '
A
' + + '
', + directives: [multipleContentTagsComponent] + })] + }); + compile(rootEl, 'main').then( (pv) => { + renderer.createView(pv.render); expect(rootEl).toHaveText('(A, BC)'); @@ -92,13 +108,18 @@ export function main() { })); it('should redistribute only direct children', inject([AsyncTestCompleter], (async) => { - var temp = '' + - '
B
A
' + - '
C
' + - '
'; - - compile(temp, [multipleContentTagsComponent]).then( (pvRefs) => { - renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'main', + inline: '' + + '
B
A
' + + '
C
' + + '
', + directives: [multipleContentTagsComponent] + })] + }); + compile(rootEl, 'main').then( (pv) => { + renderer.createView(pv.render); expect(rootEl).toHaveText('(, BAC)'); @@ -107,15 +128,21 @@ export function main() { })); it("should redistribute direct child viewcontainers when the light dom changes", inject([AsyncTestCompleter], (async) => { - var temp = '' + - '
A
' + - '
B
' + - '
'; - - compile(temp, [multipleContentTagsComponent, manualViewportDirective]).then( (pvRefs) => { - var viewRefs = renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'main', + inline: '' + + '
A
' + + '
B
' + + '
', + directives: [multipleContentTagsComponent, manualViewportDirective] + })] + }); + compile(rootEl, 'main').then( (pv) => { + var viewRefs = renderer.createView(pv.render); var vcRef = new ViewContainerRef(viewRefs[1], 1); - var vcProtoViewRef = pvRefs[2]; + var vcProtoViewRef = pv.elementBinders[0].nestedProtoView + .elementBinders[1].nestedProtoView.render; var childViewRef = renderer.createView(vcProtoViewRef)[0]; expect(rootEl).toHaveText('(, B)'); @@ -133,15 +160,21 @@ export function main() { })); it("should redistribute when the light dom changes", inject([AsyncTestCompleter], (async) => { - var temp = '' + - '
A
' + - '
B
' + - '
'; - - compile(temp, [multipleContentTagsComponent, manualViewportDirective]).then( (pvRefs) => { - var viewRefs = renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'main', + inline: '' + + '
A
' + + '
B
' + + '
', + directives: [multipleContentTagsComponent, manualViewportDirective] + })] + }); + compile(rootEl, 'main').then( (pv) => { + var viewRefs = renderer.createView(pv.render); var vcRef = new ViewContainerRef(viewRefs[1], 1); - var vcProtoViewRef = pvRefs[2]; + var vcProtoViewRef = pv.elementBinders[0].nestedProtoView + .elementBinders[1].nestedProtoView.render; var childViewRef = renderer.createView(vcProtoViewRef)[0]; expect(rootEl).toHaveText('(, B)'); @@ -159,13 +192,18 @@ export function main() { })); it("should support nested components", inject([AsyncTestCompleter], (async) => { - var temp = '' + - '
A
' + - '
B
' + - '
'; - - compile(temp, [outerWithIndirectNestedComponent]).then( (pvRefs) => { - renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'main', + inline: '' + + '
A
' + + '
B
' + + '
', + directives: [outerWithIndirectNestedComponent] + })] + }); + compile(rootEl, 'main').then( (pv) => { + renderer.createView(pv.render); expect(rootEl).toHaveText('OUTER(SIMPLE(AB))'); @@ -174,16 +212,22 @@ export function main() { })); it("should support nesting with content being direct child of a nested component", inject([AsyncTestCompleter], (async) => { - var temp = '' + - '
A
' + - '
B
' + - '
C
' + - '
'; - - compile(temp, [outerComponent, manualViewportDirective]).then( (pvRefs) => { - var viewRefs = renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'main', + inline: '' + + '
A
' + + '
B
' + + '
C
' + + '
', + directives: [outerComponent, manualViewportDirective] + })] + }); + compile(rootEl, 'main').then( (pv) => { + var viewRefs = renderer.createView(pv.render); var vcRef = new ViewContainerRef(viewRefs[1], 1); - var vcProtoViewRef = pvRefs[2]; + var vcProtoViewRef = pv.elementBinders[0].nestedProtoView + .elementBinders[1].nestedProtoView.render; var childViewRef = renderer.createView(vcProtoViewRef)[0]; expect(rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))'); @@ -196,16 +240,23 @@ export function main() { })); it('should redistribute when the shadow dom changes', inject([AsyncTestCompleter], (async) => { - var temp = '' + - '
A
' + - '
B
' + - '
C
' + - '
'; - - compile(temp, [conditionalContentComponent, autoViewportDirective]).then( (pvRefs) => { - var viewRefs = renderer.createView(pvRefs[0]); + createRenderer({ + templates: [new Template({ + componentId: 'main', + inline: '' + + '
A
' + + '
B
' + + '
C
' + + '
', + directives: [conditionalContentComponent] + })] + }); + compile(rootEl, 'main').then( (pv) => { + var viewRefs = renderer.createView(pv.render); var vcRef = new ViewContainerRef(viewRefs[2], 0); - var vcProtoViewRef = pvRefs[3]; + var vcProtoViewRef = pv.elementBinders[0].nestedProtoView + .elementBinders[0].nestedProtoView + .elementBinders[0].nestedProtoView.render; var childViewRef = renderer.createView(vcProtoViewRef)[0]; expect(rootEl).toHaveText('(, ABC)'); @@ -299,7 +350,7 @@ var autoViewportDirective = new DirectiveMetadata({ type: DirectiveMetadata.VIEWPORT_TYPE }); -var templates = [ +var componentTemplates = [ new Template({ componentId: 'simple', inline: 'SIMPLE()',