From 5dee8e26cca16605af9bcdb78dbffb8d6470f15f Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Tue, 16 Jun 2015 09:45:03 -0700 Subject: [PATCH] fix(views): remove dynamic component views, free host views, free embedded views Closes #2472 Closes #2339 BREAKING CHANGE - `Compiler.compile` has been removed, the only way to compile components dynamically is via `Compiler.compileInHost` - `DynamicComponentLoader.loadIntoExistingLocation` has changed: * renamed into `loadIntoLocation` * will always create the host element as well * requires an element with a variable inside of the host component view next to which it will load new component. - `DynamicComponentLoader.loadNextToExistingLocation` was renamed into `DynamicComponentLoader.loadNextToLocation` - `DynamicComponentLoader.loadIntoNewLocation` is removed * use `DynamicComponentLoader.loadNextToLocation` instead and then move the view nodes manually around via `DomRenderer.getRootNodes()` - `AppViewManager.{create,destroy}Free{Host,Embedded}View` was removed * use `AppViewManager.createViewInContainer` and then move the view nodes manually around via `DomRenderer.getRootNodes()` - `Renderer.detachFreeView` was removed. Use `DomRenderer.getRootNodes()` to get the root nodes of a view and detach them manually. --- .../src/core/annotations_impl/annotations.ts | 48 --- .../angular2/src/core/compiler/compiler.ts | 64 ++-- .../core/compiler/dynamic_component_loader.ts | 87 ++--- .../src/core/compiler/element_binder.ts | 4 - .../src/core/compiler/element_injector.ts | 46 +-- .../src/core/compiler/proto_view_factory.ts | 15 +- .../src/core/compiler/template_resolver.ts | 3 +- modules/angular2/src/core/compiler/view.ts | 7 +- .../src/core/compiler/view_manager.ts | 110 ++---- .../src/core/compiler/view_manager_utils.ts | 69 +--- modules/angular2/src/debug/debug_element.ts | 9 +- .../src/mock/template_resolver_mock.ts | 4 - modules/angular2/src/render/api.ts | 5 - .../angular2/src/render/dom/dom_renderer.ts | 5 - modules/angular2/src/router/router_outlet.ts | 4 +- .../test/core/compiler/compiler_spec.ts | 168 ++++++--- .../compiler/dynamic_component_loader_spec.ts | 236 ++++--------- .../core/compiler/element_injector_spec.ts | 98 +----- .../test/core/compiler/integration_spec.ts | 25 +- .../core/compiler/proto_view_factory_spec.ts | 15 +- .../core/compiler/view_container_ref_spec.ts | 2 +- .../test/core/compiler/view_manager_spec.ts | 331 +----------------- .../core/compiler/view_manager_utils_spec.ts | 40 ++- .../test/core/compiler/view_pool_spec.ts | 2 +- .../dom/dom_renderer_integration_spec.ts | 14 - .../src/components/dialog/dialog.ts | 8 +- .../src/compiler/compiler_benchmark.ts | 8 +- modules/benchmarks/src/costs/index.ts | 4 +- 28 files changed, 386 insertions(+), 1045 deletions(-) diff --git a/modules/angular2/src/core/annotations_impl/annotations.ts b/modules/angular2/src/core/annotations_impl/annotations.ts index fbe3948cbd..488db050e5 100644 --- a/modules/angular2/src/core/annotations_impl/annotations.ts +++ b/modules/angular2/src/core/annotations_impl/annotations.ts @@ -848,54 +848,6 @@ export interface ComponentArgs { * ``` * * - * Dynamically loading a component at runtime: - * - * Regular Angular components are statically resolved. Dynamic components allows to resolve a - * component at runtime - * instead by providing a placeholder into which a regular Angular component can be dynamically - * loaded. Once loaded, - * the dynamically-loaded component becomes permanent and cannot be changed. - * Dynamic components are declared just like components, but without a `@View` annotation. - * - * - * ## Example - * - * Here we have `DynamicComp` which acts as the placeholder for `HelloCmp`. At runtime, the dynamic - * component - * `DynamicComp` requests loading of the `HelloCmp` component. - * - * There is nothing special about `HelloCmp`, which is a regular Angular component. It can also be - * used in other static - * locations. - * - * ``` - * @Component({ - * selector: 'dynamic-comp' - * }) - * class DynamicComp { - * helloCmp:HelloCmp; - * constructor(loader:DynamicComponentLoader, location:ElementRef) { - * loader.load(HelloCmp, location).then((helloCmp) => { - * this.helloCmp = helloCmp; - * }); - * } - * } - * - * @Component({ - * selector: 'hello-cmp' - * }) - * @View({ - * template: "{{greeting}}" - * }) - * class HelloCmp { - * greeting:string; - * constructor() { - * this.greeting = "hello"; - * } - * } - * ``` - * - * * @exportedAs angular2/annotations */ @CONST() diff --git a/modules/angular2/src/core/compiler/compiler.ts b/modules/angular2/src/core/compiler/compiler.ts index ba4036d30b..23ff6ba2fa 100644 --- a/modules/angular2/src/core/compiler/compiler.ts +++ b/modules/angular2/src/core/compiler/compiler.ts @@ -33,6 +33,7 @@ import * as renderApi from 'angular2/src/render/api'; @Injectable() export class CompilerCache { _cache: Map = MapWrapper.create(); + _hostCache: Map = MapWrapper.create(); set(component: Type, protoView: AppProtoView): void { MapWrapper.set(this._cache, component, protoView); @@ -43,7 +44,19 @@ export class CompilerCache { return normalizeBlank(result); } - clear(): void { MapWrapper.clear(this._cache); } + setHost(component: Type, protoView: AppProtoView): void { + MapWrapper.set(this._hostCache, component, protoView); + } + + getHost(component: Type): AppProtoView { + var result = MapWrapper.get(this._hostCache, component); + return normalizeBlank(result); + } + + clear(): void { + MapWrapper.clear(this._cache); + MapWrapper.clear(this._hostCache); + } } /** @@ -94,20 +107,19 @@ export class Compiler { Compiler._assertTypeIsComponent(componentBinding); var directiveMetadata = componentBinding.metadata; - return this._render.compileHost(directiveMetadata) - .then((hostRenderPv) => { - return this._compileNestedProtoViews(componentBinding, hostRenderPv, [componentBinding]); - }) - .then((appProtoView) => { return new ProtoViewRef(appProtoView); }); - } - - compile(component: Type): Promise { - var componentBinding = this._bindDirective(component); - Compiler._assertTypeIsComponent(componentBinding); - var pvOrPromise = this._compile(componentBinding); - var pvPromise = isPromise(pvOrPromise) ? >pvOrPromise : - PromiseWrapper.resolve(pvOrPromise); - return pvPromise.then((appProtoView) => { return new ProtoViewRef(appProtoView); }); + var hostPvPromise; + var component = componentBinding.key.token; + var hostAppProtoView = this._compilerCache.getHost(component); + if (isPresent(hostAppProtoView)) { + hostPvPromise = PromiseWrapper.resolve(hostAppProtoView); + } else { + hostPvPromise = this._render.compileHost(directiveMetadata) + .then((hostRenderPv) => { + return this._compileNestedProtoViews(componentBinding, hostRenderPv, + [componentBinding]); + }); + } + return hostPvPromise.then((hostAppProtoView) => { return new ProtoViewRef(hostAppProtoView); }); } private _compile(componentBinding: DirectiveBinding): Promise| AppProtoView { @@ -128,9 +140,6 @@ export class Compiler { return pvPromise; } var template = this._templateResolver.resolve(component); - if (isBlank(template)) { - return null; - } var directives = this._flattenDirectives(template); @@ -160,16 +169,17 @@ export class Compiler { var protoViews = this._protoViewFactory.createAppProtoViews(componentBinding, renderPv, directives); var protoView = protoViews[0]; - // TODO(tbosch): we should be caching host protoViews as well! - // -> need a separate cache for this... - if (renderPv.type === renderApi.ViewType.COMPONENT && isPresent(componentBinding)) { - // Populate the cache before compiling the nested components, - // so that components can reference themselves in their template. + if (isPresent(componentBinding)) { var component = componentBinding.key.token; - this._compilerCache.set(component, protoView); - MapWrapper.delete(this._compiling, component); + if (renderPv.type === renderApi.ViewType.COMPONENT) { + // 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); + } else { + this._compilerCache.setHost(component, protoView); + } } - var nestedPVPromises = []; ListWrapper.forEach(this._collectComponentElementBinders(protoViews), (elementBinder) => { var nestedComponent = elementBinder.componentDirective; @@ -179,7 +189,7 @@ export class Compiler { if (isPromise(nestedCall)) { ListWrapper.push(nestedPVPromises, (>nestedCall).then(elementBinderDone)); - } else if (isPresent(nestedCall)) { + } else { elementBinderDone(nestedCall); } }); diff --git a/modules/angular2/src/core/compiler/dynamic_component_loader.ts b/modules/angular2/src/core/compiler/dynamic_component_loader.ts index eb8a798b8b..4881436cf0 100644 --- a/modules/angular2/src/core/compiler/dynamic_component_loader.ts +++ b/modules/angular2/src/core/compiler/dynamic_component_loader.ts @@ -25,31 +25,14 @@ export class ComponentRef { export class DynamicComponentLoader { constructor(private _compiler: Compiler, private _viewManager: AppViewManager) {} - /** - * Loads a component into the location given by the provided ElementRef. The loaded component - * receives injection as if it in the place of the provided ElementRef. - */ - loadIntoExistingLocation(typeOrBinding, location: ElementRef, - injector: Injector = null): Promise { - var binding = this._getBinding(typeOrBinding); - return this._compiler.compile(binding.token) - .then(componentProtoViewRef => { - this._viewManager.createDynamicComponentView(location, componentProtoViewRef, binding, - injector); - var component = this._viewManager.getComponent(location); - var dispose = () => { this._viewManager.destroyDynamicComponent(location); }; - return new ComponentRef(location, component, dispose); - }); - } - /** * Loads a root component that is placed at the first element that matches the * component's selector. * The loaded component receives injection normally as a hosted view. */ - loadAsRoot(typeOrBinding, overrideSelector: string = null, + loadAsRoot(typeOrBinding: Type | Binding, overrideSelector: string = null, injector: Injector = null): Promise { - return this._compiler.compileInHost(this._getBinding(typeOrBinding)) + return this._compiler.compileInHost(typeOrBinding) .then(hostProtoViewRef => { var hostViewRef = this._viewManager.createRootHostView(hostProtoViewRef, overrideSelector, injector); @@ -62,55 +45,37 @@ export class DynamicComponentLoader { } /** - * Loads a component into a free host view that is not yet attached to - * a parent on the render side, although it is attached to a parent in the injector hierarchy. - * The loaded component receives injection normally as a hosted view. + * Loads a component into the component view of the provided ElementRef + * next to the element with the given name + * The loaded component receives + * injection normally as a hosted view. */ - loadIntoNewLocation(typeOrBinding, parentComponentLocation: ElementRef, - injector: Injector = null): Promise { - return this._compiler.compileInHost(this._getBinding(typeOrBinding)) - .then(hostProtoViewRef => { - var hostViewRef = this._viewManager.createFreeHostView(parentComponentLocation, - hostProtoViewRef, injector); - var newLocation = new ElementRef(hostViewRef, 0); - var component = this._viewManager.getComponent(newLocation); - - var dispose = () => { - this._viewManager.destroyFreeHostView(parentComponentLocation, hostViewRef); - }; - return new ComponentRef(newLocation, component, dispose); - }); + loadIntoLocation(typeOrBinding: Type | Binding, hostLocation: ElementRef, anchorName: string, + injector: Injector = null): Promise { + return this.loadNextToLocation( + typeOrBinding, this._viewManager.getNamedElementInComponentView(hostLocation, anchorName), + injector); } /** * Loads a component next to the provided ElementRef. The loaded component receives * injection normally as a hosted view. */ - loadNextToExistingLocation(typeOrBinding, location: ElementRef, - injector: Injector = null): Promise { - var binding = this._getBinding(typeOrBinding); - return this._compiler.compileInHost(binding).then(hostProtoViewRef => { - var viewContainer = this._viewManager.getViewContainer(location); - var hostViewRef = - viewContainer.create(hostProtoViewRef, viewContainer.length, null, injector); - var newLocation = new ElementRef(hostViewRef, 0); - var component = this._viewManager.getComponent(newLocation); + loadNextToLocation(typeOrBinding: Type | Binding, location: ElementRef, + injector: Injector = null): Promise { + return this._compiler.compileInHost(typeOrBinding) + .then(hostProtoViewRef => { + var viewContainer = this._viewManager.getViewContainer(location); + var hostViewRef = + viewContainer.create(hostProtoViewRef, viewContainer.length, null, injector); + var newLocation = new ElementRef(hostViewRef, 0); + var component = this._viewManager.getComponent(newLocation); - var dispose = () => { - var index = viewContainer.indexOf(hostViewRef); - viewContainer.remove(index); - }; - return new ComponentRef(newLocation, component, dispose); - }); - } - - private _getBinding(typeOrBinding): Binding { - var binding; - if (typeOrBinding instanceof Binding) { - binding = typeOrBinding; - } else { - binding = bind(typeOrBinding).toClass(typeOrBinding); - } - return binding; + var dispose = () => { + var index = viewContainer.indexOf(hostViewRef); + viewContainer.remove(index); + }; + return new ComponentRef(newLocation, component, dispose); + }); } } diff --git a/modules/angular2/src/core/compiler/element_binder.ts b/modules/angular2/src/core/compiler/element_binder.ts index 0e25abdfa6..74e3724cff 100644 --- a/modules/angular2/src/core/compiler/element_binder.ts +++ b/modules/angular2/src/core/compiler/element_binder.ts @@ -24,10 +24,6 @@ export class ElementBinder { return isPresent(this.componentDirective) && isPresent(this.nestedProtoView); } - hasDynamicComponent() { - return isPresent(this.componentDirective) && isBlank(this.nestedProtoView); - } - hasEmbeddedProtoView() { return !isPresent(this.componentDirective) && isPresent(this.nestedProtoView); } diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index f3b1d55c38..4e9f8642f8 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -662,9 +662,6 @@ export class ElementInjector extends TreeNode { private _preBuiltObjects = null; private _constructionCounter: number = 0; - private _dynamicallyCreatedComponent: any; - private _dynamicallyCreatedComponentBinding: DirectiveBinding; - // Queries are added during construction or linking with a new parent. // They are never removed. private _query0: QueryRef; @@ -693,20 +690,10 @@ export class ElementInjector extends TreeNode { this._lightDomAppInjector = null; this._shadowDomAppInjector = null; this._strategy.callOnDestroy(); - this.destroyDynamicComponent(); this._strategy.clearInstances(); this._constructionCounter = 0; } - destroyDynamicComponent(): void { - if (isPresent(this._dynamicallyCreatedComponentBinding) && - this._dynamicallyCreatedComponentBinding.callOnDestroy) { - this._dynamicallyCreatedComponent.onDestroy(); - this._dynamicallyCreatedComponentBinding = null; - this._dynamicallyCreatedComponent = null; - } - } - onAllChangesDone(): void { if (isPresent(this._query0) && this._query0.originator === this) this._query0.list.fireCallbacks(); @@ -743,14 +730,6 @@ export class ElementInjector extends TreeNode { } } - dynamicallyCreateComponent(componentDirective: DirectiveBinding, parentInjector: Injector): any { - this._shadowDomAppInjector = - this._createShadowDomAppInjector(componentDirective, parentInjector); - this._dynamicallyCreatedComponentBinding = componentDirective; - this._dynamicallyCreatedComponent = this._new(this._dynamicallyCreatedComponentBinding); - return this._dynamicallyCreatedComponent; - } - private _checkShadowDomAppInjector(shadowDomAppInjector: Injector): void { if (this._proto._firstBindingIsComponent && isBlank(shadowDomAppInjector)) { throw new BaseException( @@ -761,18 +740,7 @@ export class ElementInjector extends TreeNode { } } - get(token): any { - if (this._isDynamicallyLoadedComponent(token)) { - return this._dynamicallyCreatedComponent; - } - - return this._getByKey(Key.get(token), self, false, null); - } - - private _isDynamicallyLoadedComponent(token): boolean { - return isPresent(this._dynamicallyCreatedComponentBinding) && - Key.get(token) === this._dynamicallyCreatedComponentBinding.key; - } + get(token): any { return this._getByKey(Key.get(token), self, false, null); } hasDirective(type: Type): boolean { return this._strategy.getObjByKeyId(Key.get(type).id, LIGHT_DOM_AND_SHADOW_DOM) !== _undefined; @@ -796,17 +764,10 @@ export class ElementInjector extends TreeNode { return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef()); } - getDynamicallyLoadedComponent(): any { return this._dynamicallyCreatedComponent; } - directParent(): ElementInjector { return this._proto.distanceToParent < 2 ? this.parent : null; } private _isComponentKey(key: Key): boolean { return this._strategy.isComponentKey(key); } - private _isDynamicallyLoadedComponentKey(key: Key): boolean { - return isPresent(this._dynamicallyCreatedComponentBinding) && - key.id === this._dynamicallyCreatedComponentBinding.key.id; - } - _new(binding: ResolvedBinding): any { if (this._constructionCounter++ > this._strategy.getMaxDirectives()) { throw new CyclicDependencyError(binding.key); @@ -1113,8 +1074,6 @@ export class ElementInjector extends TreeNode { if (isPresent(this._host) && this._host._isComponentKey(key)) { return this._host.getComponent(); - } else if (isPresent(this._host) && this._host._isDynamicallyLoadedComponentKey(key)) { - return this._host.getDynamicallyLoadedComponent(); } else if (optional) { return this._appInjector(requestor).getOptional(key); } else { @@ -1123,8 +1082,7 @@ export class ElementInjector extends TreeNode { } private _appInjector(requestor: Key): Injector { - if (isPresent(requestor) && - (this._isComponentKey(requestor) || this._isDynamicallyLoadedComponentKey(requestor))) { + if (isPresent(requestor) && this._isComponentKey(requestor)) { return this._shadowDomAppInjector; } else { return this._lightDomAppInjector; diff --git a/modules/angular2/src/core/compiler/proto_view_factory.ts b/modules/angular2/src/core/compiler/proto_view_factory.ts index 6d7ddaa95b..cb53a7c15e 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.ts +++ b/modules/angular2/src/core/compiler/proto_view_factory.ts @@ -231,7 +231,8 @@ function _createAppProtoView( renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector, variableBindings: Map, allDirectives: List): AppProtoView { var elementBinders = renderProtoView.elementBinders; - var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings); + var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings, + createVariableLocations(elementBinders)); _createElementBinders(protoView, elementBinders, allDirectives); _bindDirectiveEvents(protoView, elementBinders); @@ -276,6 +277,18 @@ function _createVariableNames(parentVariableNames, renderProtoView): List): Map { + var variableLocations = MapWrapper.create(); + for (var i = 0; i < elementBinders.length; i++) { + var binder = elementBinders[i]; + MapWrapper.forEach(binder.variableBindings, (mappedName, varName) => { + MapWrapper.set(variableLocations, mappedName, i); + }); + } + return variableLocations; +} + function _createElementBinders(protoView, elementBinders, allDirectiveBindings) { for (var i = 0; i < elementBinders.length; i++) { var renderElementBinder = elementBinders[i]; diff --git a/modules/angular2/src/core/compiler/template_resolver.ts b/modules/angular2/src/core/compiler/template_resolver.ts index c70d62dc29..f0548a55d8 100644 --- a/modules/angular2/src/core/compiler/template_resolver.ts +++ b/modules/angular2/src/core/compiler/template_resolver.ts @@ -30,7 +30,6 @@ export class TemplateResolver { return annotation; } } - // No annotation = dynamic component! - return null; + throw new BaseException(`No View annotation found on component ${stringify(component)}`); } } diff --git a/modules/angular2/src/core/compiler/view.ts b/modules/angular2/src/core/compiler/view.ts index d07fe2a9de..adbbdd5ebe 100644 --- a/modules/angular2/src/core/compiler/view.ts +++ b/modules/angular2/src/core/compiler/view.ts @@ -25,7 +25,6 @@ import {EventDispatcher} from 'angular2/src/render/api'; export class AppViewContainer { // The order in this list matches the DOM order. views: List = []; - freeViews: List = []; } /** @@ -39,9 +38,6 @@ export class AppView implements ChangeDispatcher, EventDispatcher { elementInjectors: List = null; changeDetector: ChangeDetector = null; componentChildViews: List = null; - /// Host views that were added by an imperative view. - /// This is a dynamically growing / shrinking array. - freeHostViews: List = []; viewContainers: List; preBuiltObjects: List = null; @@ -170,7 +166,8 @@ export class AppProtoView { constructor(public render: renderApi.RenderProtoViewRef, public protoChangeDetector: ProtoChangeDetector, - public variableBindings: Map) { + public variableBindings: Map, + public variableLocations: Map) { if (isPresent(variableBindings)) { MapWrapper.forEach(variableBindings, (templateName, _) => { MapWrapper.set(this.protoLocals, templateName, null); diff --git a/modules/angular2/src/core/compiler/view_manager.ts b/modules/angular2/src/core/compiler/view_manager.ts index ec5213fb73..54c3816df3 100644 --- a/modules/angular2/src/core/compiler/view_manager.ts +++ b/modules/angular2/src/core/compiler/view_manager.ts @@ -8,6 +8,7 @@ import {Renderer, RenderViewRef} from 'angular2/src/render/api'; import {AppViewManagerUtils} from './view_manager_utils'; import {AppViewPool} from './view_pool'; import {AppViewListener} from './view_listener'; +import {MapWrapper} from 'angular2/src/facade/collection'; /** * Entry point for creating, moving views in the view hierarchy and destroying views. @@ -30,33 +31,30 @@ export class AppViewManager { return hostView.elementInjectors[location.boundElementIndex].getViewContainerRef(); } + /** + * Returns an ElementRef for the element with the given variable name + * in the component view of the component at the provided ElementRef. + */ + getNamedElementInComponentView(hostLocation: ElementRef, variableName: string): ElementRef { + var hostView = internalView(hostLocation.parentView); + var boundElementIndex = hostLocation.boundElementIndex; + var componentView = hostView.componentChildViews[boundElementIndex]; + if (isBlank(componentView)) { + throw new BaseException(`There is no component directive at element ${boundElementIndex}`); + } + var elementIndex = MapWrapper.get(componentView.proto.variableLocations, variableName); + if (isBlank(elementIndex)) { + throw new BaseException(`Could not find variable ${variableName}`); + } + return new ElementRef(new ViewRef(componentView), elementIndex); + } + getComponent(hostLocation: ElementRef): any { var hostView = internalView(hostLocation.parentView); var boundElementIndex = hostLocation.boundElementIndex; return this._utils.getComponentInstance(hostView, boundElementIndex); } - createDynamicComponentView(hostLocation: ElementRef, componentProtoViewRef: ProtoViewRef, - componentBinding: Binding, injector: Injector): ViewRef { - var componentProtoView = internalProtoView(componentProtoViewRef); - var hostView = internalView(hostLocation.parentView); - var boundElementIndex = hostLocation.boundElementIndex; - var binder = hostView.proto.elementBinders[boundElementIndex]; - if (!binder.hasDynamicComponent()) { - throw new BaseException( - `There is no dynamic component directive at element ${boundElementIndex}`) - } - var componentView = this._createPooledView(componentProtoView); - this._renderer.attachComponentView(hostView.render, boundElementIndex, componentView.render); - this._utils.attachComponentView(hostView, boundElementIndex, componentView); - this._utils.hydrateDynamicComponentInElementInjector(hostView, boundElementIndex, - componentBinding, injector); - this._utils.hydrateComponentView(hostView, boundElementIndex); - this._viewHydrateRecurse(componentView); - - return new ViewRef(componentView); - } - createRootHostView(hostProtoViewRef: ProtoViewRef, overrideSelector: string, injector: Injector): ViewRef { var hostProtoView = internalProtoView(hostProtoViewRef); @@ -86,51 +84,6 @@ export class AppViewManager { this._viewListener.viewDestroyed(hostView); } - createFreeHostView(parentComponentLocation: ElementRef, hostProtoViewRef: ProtoViewRef, - injector: Injector): ViewRef { - var hostProtoView = internalProtoView(hostProtoViewRef); - var hostView = this._createPooledView(hostProtoView); - var parentComponentHostView = internalView(parentComponentLocation.parentView); - var parentComponentBoundElementIndex = parentComponentLocation.boundElementIndex; - this._utils.attachAndHydrateFreeHostView(parentComponentHostView, - parentComponentBoundElementIndex, hostView, injector); - this._viewHydrateRecurse(hostView); - return new ViewRef(hostView); - } - - destroyFreeHostView(parentComponentLocation: ElementRef, hostViewRef: ViewRef) { - var hostView = internalView(hostViewRef); - var parentView = internalView(parentComponentLocation.parentView) - .componentChildViews[parentComponentLocation.boundElementIndex]; - this._destroyFreeHostView(parentView, hostView); - } - - createFreeEmbeddedView(location: ElementRef, protoViewRef: ProtoViewRef, - injector: Injector = null): ViewRef { - var protoView = internalProtoView(protoViewRef); - var parentView = internalView(location.parentView); - var boundElementIndex = location.boundElementIndex; - - var view = this._createPooledView(protoView); - this._utils.attachAndHydrateFreeEmbeddedView(parentView, boundElementIndex, view, injector); - this._viewHydrateRecurse(view); - return new ViewRef(view); - } - - destroyFreeEmbeddedView(location: ElementRef, viewRef: ViewRef) { - var parentView = internalView(location.parentView); - var boundElementIndex = location.boundElementIndex; - this._destroyFreeEmbeddedView(parentView, boundElementIndex, internalView(viewRef)); - } - - destroyDynamicComponent(location: ElementRef) { - var hostView = internalView(location.parentView); - var ei = hostView.elementInjectors[location.boundElementIndex]; - var componentView = hostView.componentChildViews[location.boundElementIndex]; - ei.destroyDynamicComponent(); - this._destroyComponentView(hostView, location.boundElementIndex, componentView); - } - createViewInContainer(viewContainerLocation: ElementRef, atIndex: number, protoViewRef: ProtoViewRef, context: ElementRef = null, injector: Injector = null): ViewRef { @@ -239,20 +192,6 @@ export class AppViewManager { this._destroyPooledView(componentView); } - _destroyFreeHostView(parentView, hostView) { - this._viewDehydrateRecurse(hostView, true); - this._renderer.detachFreeView(hostView.render); - this._utils.detachFreeHostView(parentView, hostView); - this._destroyPooledView(hostView); - } - - _destroyFreeEmbeddedView(parentView, boundElementIndex, view) { - this._viewDehydrateRecurse(view, false); - this._renderer.detachFreeView(view.render); - this._utils.detachFreeEmbeddedView(parentView, boundElementIndex, view); - this._destroyPooledView(view); - } - _viewHydrateRecurse(view: viewModule.AppView) { this._renderer.hydrateView(view.render); @@ -272,7 +211,7 @@ export class AppViewManager { for (var i = 0; i < binders.length; i++) { var componentView = view.componentChildViews[i]; if (isPresent(componentView)) { - if (binders[i].hasDynamicComponent() || forceDestroyComponents) { + if (forceDestroyComponents) { this._destroyComponentView(view, i, componentView); } else { this._viewDehydrateRecurse(componentView, false); @@ -283,16 +222,7 @@ export class AppViewManager { for (var j = vc.views.length - 1; j >= 0; j--) { this._destroyViewInContainer(view, i, j); } - for (var j = vc.freeViews.length - 1; j >= 0; j--) { - this._destroyFreeEmbeddedView(view, i, j); - } } } - - // freeHostViews - for (var i = view.freeHostViews.length - 1; i >= 0; i--) { - var hostView = view.freeHostViews[i]; - this._destroyFreeHostView(view, hostView); - } } } diff --git a/modules/angular2/src/core/compiler/view_manager_utils.ts b/modules/angular2/src/core/compiler/view_manager_utils.ts index 52031e1105..54f0f765e3 100644 --- a/modules/angular2/src/core/compiler/view_manager_utils.ts +++ b/modules/angular2/src/core/compiler/view_manager_utils.ts @@ -6,21 +6,15 @@ import * as viewModule from './view'; import * as avmModule from './view_manager'; import {Renderer} from 'angular2/src/render/api'; import {Locals} from 'angular2/change_detection'; -import {DirectiveResolver} from './directive_resolver'; import {RenderViewRef} from 'angular2/src/render/api'; @Injectable() export class AppViewManagerUtils { - constructor(public _directiveResolver: DirectiveResolver) {} + constructor() {} getComponentInstance(parentView: viewModule.AppView, boundElementIndex: number): any { - var binder = parentView.proto.elementBinders[boundElementIndex]; var eli = parentView.elementInjectors[boundElementIndex]; - if (binder.hasDynamicComponent()) { - return eli.getDynamicallyLoadedComponent(); - } else { - return eli.getComponent(); - } + return eli.getComponent(); } createView(protoView: viewModule.AppProtoView, renderView: RenderViewRef, @@ -92,44 +86,6 @@ export class AppViewManagerUtils { this._hydrateView(hostView, injector, null, new Object(), null); } - attachAndHydrateFreeHostView(parentComponentHostView: viewModule.AppView, - parentComponentBoundElementIndex: number, - hostView: viewModule.AppView, injector: Injector = null) { - var hostElementInjector = - parentComponentHostView.elementInjectors[parentComponentBoundElementIndex]; - var parentView = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex]; - parentView.changeDetector.addChild(hostView.changeDetector); - ListWrapper.push(parentView.freeHostViews, hostView); - this._hydrateView(hostView, injector, hostElementInjector, new Object(), null); - } - - detachFreeHostView(parentView: viewModule.AppView, hostView: viewModule.AppView) { - parentView.changeDetector.removeChild(hostView.changeDetector); - ListWrapper.remove(parentView.freeHostViews, hostView); - } - - attachAndHydrateFreeEmbeddedView(parentView: viewModule.AppView, boundElementIndex: number, - view: viewModule.AppView, injector: Injector = null) { - parentView.changeDetector.addChild(view.changeDetector); - var viewContainer = this._getOrCreateViewContainer(parentView, boundElementIndex); - ListWrapper.push(viewContainer.freeViews, view); - var elementInjector = parentView.elementInjectors[boundElementIndex]; - for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) { - view.rootElementInjectors[i].link(elementInjector); - } - this._hydrateView(view, injector, elementInjector, parentView.context, parentView.locals); - } - - detachFreeEmbeddedView(parentView: viewModule.AppView, boundElementIndex: number, - view: viewModule.AppView) { - var viewContainer = parentView.viewContainers[boundElementIndex]; - view.changeDetector.remove(); - ListWrapper.remove(viewContainer.freeViews, view); - for (var i = 0; i < view.rootElementInjectors.length; ++i) { - view.rootElementInjectors[i].unlink(); - } - } - attachViewInContainer(parentView: viewModule.AppView, boundElementIndex: number, contextView: viewModule.AppView, contextBoundElementIndex: number, atIndex: number, view: viewModule.AppView) { @@ -172,23 +128,12 @@ export class AppViewManagerUtils { } var viewContainer = parentView.viewContainers[boundElementIndex]; var view = viewContainer.views[atIndex]; - var elementInjector = contextView.elementInjectors[contextBoundElementIndex].getHost(); - this._hydrateView(view, injector, elementInjector, contextView.context, contextView.locals); - } - - hydrateDynamicComponentInElementInjector(hostView: viewModule.AppView, boundElementIndex: number, - componentBinding: Binding, injector: Injector = null) { - var elementInjector = hostView.elementInjectors[boundElementIndex]; - if (isPresent(elementInjector.getDynamicallyLoadedComponent())) { - throw new BaseException( - `There already is a dynamic component loaded at element ${boundElementIndex}`); + var elementInjector = contextView.elementInjectors[contextBoundElementIndex]; + if (isBlank(elementInjector.getHost()) && isBlank(injector)) { + injector = elementInjector.getShadowDomAppInjector(); } - if (isBlank(injector)) { - injector = elementInjector.getLightDomAppInjector(); - } - var annotation = this._directiveResolver.resolve(componentBinding.token); - var componentDirective = eli.DirectiveBinding.createFromBinding(componentBinding, annotation); - elementInjector.dynamicallyCreateComponent(componentDirective, injector); + this._hydrateView(view, injector, elementInjector.getHost(), contextView.context, + contextView.locals); } _hydrateView(view: viewModule.AppView, appInjector: Injector, diff --git a/modules/angular2/src/debug/debug_element.ts b/modules/angular2/src/debug/debug_element.ts index 127cd16d37..8fa81775fd 100644 --- a/modules/angular2/src/debug/debug_element.ts +++ b/modules/angular2/src/debug/debug_element.ts @@ -35,19 +35,14 @@ export class DebugElement { return this._elementInjector.getComponent(); } - get dynamicallyCreatedComponentInstance(): any { - if (!isPresent(this._elementInjector)) { - return null; - } - return this._elementInjector.getDynamicallyLoadedComponent(); - } - get domElement(): any { return resolveInternalDomView(this._parentView.render) .boundElements[this._boundElementIndex] .element; } + get elementRef(): ElementRef { return this._elementInjector.getElementRef(); } + getDirectiveInstance(directiveIndex: number): any { return this._elementInjector.getDirectiveAtIndex(directiveIndex); } diff --git a/modules/angular2/src/mock/template_resolver_mock.ts b/modules/angular2/src/mock/template_resolver_mock.ts index 1217ab6d20..b332a545ca 100644 --- a/modules/angular2/src/mock/template_resolver_mock.ts +++ b/modules/angular2/src/mock/template_resolver_mock.ts @@ -80,10 +80,6 @@ export class MockTemplateResolver extends TemplateResolver { if (isBlank(view)) { view = super.resolve(component); } - if (isBlank(view)) { - // dynamic components - return null; - } var directives = view.directives; var overrides = MapWrapper.get(this._directiveOverrides, component); diff --git a/modules/angular2/src/render/api.ts b/modules/angular2/src/render/api.ts index 063c0cf43b..dd440ad7a6 100644 --- a/modules/angular2/src/render/api.ts +++ b/modules/angular2/src/render/api.ts @@ -307,11 +307,6 @@ export class Renderer { return null; } - /** - * Detaches a free view's element from the DOM. - */ - detachFreeView(view: RenderViewRef) {} - /** * Creates a regular view out of the given ProtoView */ diff --git a/modules/angular2/src/render/dom/dom_renderer.ts b/modules/angular2/src/render/dom/dom_renderer.ts index 58ef4b6fe7..a286032968 100644 --- a/modules/angular2/src/render/dom/dom_renderer.ts +++ b/modules/angular2/src/render/dom/dom_renderer.ts @@ -44,11 +44,6 @@ export class DomRenderer extends Renderer { return new DomViewRef(this._createView(hostProtoView, element)); } - detachFreeView(viewRef: RenderViewRef) { - var view = resolveInternalDomView(viewRef); - this._removeViewNodes(view); - } - createView(protoViewRef: RenderProtoViewRef): RenderViewRef { var protoView = resolveInternalDomProtoView(protoViewRef); return new DomViewRef(this._createView(protoView, null)); diff --git a/modules/angular2/src/router/router_outlet.ts b/modules/angular2/src/router/router_outlet.ts index fb83bf5e30..fbb4edb95a 100644 --- a/modules/angular2/src/router/router_outlet.ts +++ b/modules/angular2/src/router/router_outlet.ts @@ -64,8 +64,8 @@ export class RouterOutlet { ]); return this.deactivate() - .then((_) => this._loader.loadNextToExistingLocation(instruction.component, - this._elementRef, outletInjector)) + .then((_) => this._loader.loadNextToLocation(instruction.component, this._elementRef, + outletInjector)) .then((componentRef) => { this._componentRef = componentRef; return this._childRouter.commit(instruction.child); diff --git a/modules/angular2/test/core/compiler/compiler_spec.ts b/modules/angular2/test/core/compiler/compiler_spec.ts index ac69bf9447..ef8f758b62 100644 --- a/modules/angular2/test/core/compiler/compiler_spec.ts +++ b/modules/angular2/test/core/compiler/compiler_spec.ts @@ -41,13 +41,19 @@ import {RenderCompiler} from 'angular2/src/render/api'; export function main() { describe('compiler', function() { var directiveResolver, tplResolver, renderCompiler, protoViewFactory, cmpUrlMapper, - renderCompileRequests; + renderCompileRequests, rootProtoView; beforeEach(() => { directiveResolver = new DirectiveResolver(); tplResolver = new FakeTemplateResolver(); cmpUrlMapper = new RuntimeComponentUrlMapper(); renderCompiler = new SpyRenderCompiler(); + renderCompiler.spy('compileHost') + .andCallFake((componentId) => { + return PromiseWrapper.resolve(createRenderProtoView( + [createRenderComponentElementBinder(0)], renderApi.ViewType.HOST)); + }); + rootProtoView = createRootProtoView(directiveResolver, MainComponent); }); function createCompiler(renderCompileResults: List, @@ -68,8 +74,9 @@ export function main() { function captureTemplate(template: viewAnn.View): Promise { tplResolver.setView(MainComponent, template); - var compiler = createCompiler([createRenderProtoView()], [[createProtoView()]]); - return compiler.compile(MainComponent) + var compiler = + createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]); + return compiler.compileInHost(MainComponent) .then((_) => { expect(renderCompileRequests.length).toBe(1); return renderCompileRequests[0]; @@ -249,14 +256,14 @@ export function main() { describe('call ProtoViewFactory', () => { - it('should pass the render protoView', inject([AsyncTestCompleter], (async) => { + it('should pass the ProtoViewDto', inject([AsyncTestCompleter], (async) => { tplResolver.setView(MainComponent, new viewAnn.View({template: '
'})); var renderProtoView = createRenderProtoView(); var expectedProtoView = createProtoView(); - var compiler = createCompiler([renderProtoView], [[expectedProtoView]]); - compiler.compile(MainComponent) + var compiler = createCompiler([renderProtoView], [[rootProtoView], [expectedProtoView]]); + compiler.compileInHost(MainComponent) .then((_) => { - var request = protoViewFactory.requests[0]; + var request = protoViewFactory.requests[1]; expect(request[1]).toBe(renderProtoView); async.done(); }); @@ -264,10 +271,11 @@ export function main() { it('should pass the component binding', inject([AsyncTestCompleter], (async) => { tplResolver.setView(MainComponent, new viewAnn.View({template: '
'})); - var compiler = createCompiler([createRenderProtoView()], [[createProtoView()]]); - compiler.compile(MainComponent) + var compiler = + createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]); + compiler.compileInHost(MainComponent) .then((_) => { - var request = protoViewFactory.requests[0]; + var request = protoViewFactory.requests[1]; expect(request[0].key.token).toBe(MainComponent); async.done(); }); @@ -277,10 +285,11 @@ export function main() { tplResolver.setView( MainComponent, new viewAnn.View({template: '
', directives: [SomeDirective]})); - var compiler = createCompiler([createRenderProtoView()], [[createProtoView()]]); - compiler.compile(MainComponent) + var compiler = + createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]); + compiler.compileInHost(MainComponent) .then((_) => { - var request = protoViewFactory.requests[0]; + var request = protoViewFactory.requests[1]; var binding = request[2][0]; expect(binding.key.token).toBe(SomeDirective); async.done(); @@ -290,12 +299,11 @@ export function main() { it('should use the protoView of the ProtoViewFactory', inject([AsyncTestCompleter], (async) => { tplResolver.setView(MainComponent, new viewAnn.View({template: '
'})); - var renderProtoView = createRenderProtoView(); - var expectedProtoView = createProtoView(); - var compiler = createCompiler([renderProtoView], [[expectedProtoView]]); - compiler.compile(MainComponent) + var compiler = + createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]); + compiler.compileInHost(MainComponent) .then((protoViewRef) => { - expect(internalProtoView(protoViewRef)).toBe(expectedProtoView); + expect(internalProtoView(protoViewRef)).toBe(rootProtoView); async.done(); }); })); @@ -312,10 +320,11 @@ export function main() { createRenderProtoView([createRenderComponentElementBinder(0)]), createRenderProtoView() ], - [[mainProtoView], [nestedProtoView]]); - compiler.compile(MainComponent) + [[rootProtoView], [mainProtoView], [nestedProtoView]]); + compiler.compileInHost(MainComponent) .then((protoViewRef) => { - expect(internalProtoView(protoViewRef)).toBe(mainProtoView); + expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView) + .toBe(mainProtoView); expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView); async.done(); }); @@ -328,35 +337,68 @@ export function main() { var viewportProtoView = createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]); var nestedProtoView = createProtoView(); - var compiler = createCompiler([ - createRenderProtoView([ - createRenderViewportElementBinder(createRenderProtoView( - [createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED)) - ]), - createRenderProtoView() - ], - [[mainProtoView, viewportProtoView], [nestedProtoView]]); - compiler.compile(MainComponent) + var compiler = createCompiler( + [ + createRenderProtoView([ + createRenderViewportElementBinder(createRenderProtoView( + [createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED)) + ]), + createRenderProtoView() + ], + [[rootProtoView], [mainProtoView, viewportProtoView], [nestedProtoView]]); + compiler.compileInHost(MainComponent) .then((protoViewRef) => { - expect(internalProtoView(protoViewRef)).toBe(mainProtoView); + expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView) + .toBe(mainProtoView); expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView); async.done(); }); })); - it('should cache compiled components', inject([AsyncTestCompleter], (async) => { + it('should cache compiled host components', inject([AsyncTestCompleter], (async) => { tplResolver.setView(MainComponent, new viewAnn.View({template: '
'})); - var renderProtoView = createRenderProtoView(); - var expectedProtoView = createProtoView(); - var compiler = createCompiler([renderProtoView], [[expectedProtoView]]); - compiler.compile(MainComponent) + var mainPv = createProtoView(); + var compiler = createCompiler([createRenderProtoView()], [[rootProtoView], [mainPv]]); + compiler.compileInHost(MainComponent) .then((protoViewRef) => { - expect(internalProtoView(protoViewRef)).toBe(expectedProtoView); - return compiler.compile(MainComponent); + expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView) + .toBe(mainPv); + return compiler.compileInHost(MainComponent); }) .then((protoViewRef) => { - expect(internalProtoView(protoViewRef)).toBe(expectedProtoView); + expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView) + .toBe(mainPv); + async.done(); + }); + })); + + it('should cache compiled nested components', inject([AsyncTestCompleter], (async) => { + tplResolver.setView(MainComponent, new viewAnn.View({template: '
'})); + tplResolver.setView(MainComponent2, new viewAnn.View({template: '
'})); + tplResolver.setView(NestedComponent, new viewAnn.View({template: '
'})); + var rootProtoView2 = createRootProtoView(directiveResolver, MainComponent2); + var mainPv = + createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]); + var nestedPv = createProtoView([]); + var compiler = createCompiler( + [createRenderProtoView(), createRenderProtoView(), createRenderProtoView()], + [[rootProtoView], [mainPv], [nestedPv], [rootProtoView2], [mainPv]]); + compiler.compileInHost(MainComponent) + .then((protoViewRef) => { + expect(internalProtoView(protoViewRef) + .elementBinders[0] + .nestedProtoView.elementBinders[0] + .nestedProtoView) + .toBe(nestedPv); + return compiler.compileInHost(MainComponent2); + }) + .then((protoViewRef) => { + expect(internalProtoView(protoViewRef) + .elementBinders[0] + .nestedProtoView.elementBinders[0] + .nestedProtoView) + .toBe(nestedPv); async.done(); }); })); @@ -365,36 +407,40 @@ export function main() { tplResolver.setView(MainComponent, new viewAnn.View({template: '
'})); var renderProtoViewCompleter = PromiseWrapper.completer(); var expectedProtoView = createProtoView(); - var compiler = createCompiler([renderProtoViewCompleter.promise], [[expectedProtoView]]); + var compiler = createCompiler([renderProtoViewCompleter.promise], + [[rootProtoView], [rootProtoView], [expectedProtoView]]); + var result = PromiseWrapper.all([ + compiler.compileInHost(MainComponent), + compiler.compileInHost(MainComponent), + renderProtoViewCompleter.promise + ]); renderProtoViewCompleter.resolve(createRenderProtoView()); - PromiseWrapper.all([compiler.compile(MainComponent), compiler.compile(MainComponent)]) - .then((protoViewRefs) => { - expect(internalProtoView(protoViewRefs[0])).toBe(expectedProtoView); - expect(internalProtoView(protoViewRefs[1])).toBe(expectedProtoView); - async.done(); - }); + result.then((protoViewRefs) => { + expect(internalProtoView(protoViewRefs[0]).elementBinders[0].nestedProtoView) + .toBe(expectedProtoView); + expect(internalProtoView(protoViewRefs[1]).elementBinders[0].nestedProtoView) + .toBe(expectedProtoView); + async.done(); + }); })); it('should allow recursive components', inject([AsyncTestCompleter], (async) => { tplResolver.setView(MainComponent, new viewAnn.View({template: '
'})); var mainProtoView = createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]); - var compiler = createCompiler( - [createRenderProtoView([createRenderComponentElementBinder(0)])], [[mainProtoView]]); - compiler.compile(MainComponent) + var compiler = + createCompiler([createRenderProtoView([createRenderComponentElementBinder(0)])], + [[rootProtoView], [mainProtoView]]); + compiler.compileInHost(MainComponent) .then((protoViewRef) => { - expect(internalProtoView(protoViewRef)).toBe(mainProtoView); + expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView) + .toBe(mainProtoView); expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(mainProtoView); async.done(); }); })); it('should create host proto views', inject([AsyncTestCompleter], (async) => { - renderCompiler.spy('compileHost') - .andCallFake((componentId) => { - return PromiseWrapper.resolve(createRenderProtoView( - [createRenderComponentElementBinder(0)], renderApi.ViewType.HOST)); - }); tplResolver.setView(MainComponent, new viewAnn.View({template: '
'})); var rootProtoView = createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]); @@ -411,7 +457,7 @@ export function main() { it('should throw for non component types', () => { var compiler = createCompiler([], []); - expect(() => compiler.compile(SomeDirective)) + expect(() => compiler.compileInHost(SomeDirective)) .toThrowError( `Could not load '${stringify(SomeDirective)}' because it is not a component.`); }); @@ -424,7 +470,7 @@ function createDirectiveBinding(directiveResolver, type) { } function createProtoView(elementBinders = null) { - var pv = new AppProtoView(null, null, MapWrapper.create()); + var pv = new AppProtoView(null, null, MapWrapper.create(), null); if (isBlank(elementBinders)) { elementBinders = []; } @@ -462,10 +508,18 @@ function createRenderViewportElementBinder(nestedProtoView) { return new renderApi.ElementBinder({nestedProtoView: nestedProtoView}); } +function createRootProtoView(directiveResolver, type) { + return createProtoView([createComponentElementBinder(directiveResolver, type)]); +} + @Component({selector: 'main-comp'}) class MainComponent { } +@Component({selector: 'main-comp2'}) +class MainComponent2 { +} + @Component({selector: 'nested'}) class NestedComponent { } diff --git a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts index ace2bd3958..65f69fdd63 100644 --- a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts +++ b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts @@ -12,123 +12,101 @@ import { beforeEachBindings, it, xit, - viewRootNodes + viewRootNodes, + TestComponentBuilder } from 'angular2/test_lib'; import {TestBed, ViewProxy} from 'angular2/src/test_lib/test_bed'; import {Injector} from 'angular2/di'; import {Component, View, onDestroy} from 'angular2/annotations'; -import {Locals} from 'angular2/change_detection'; import * as viewAnn from 'angular2/src/core/annotations_impl/view'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {ElementRef} from 'angular2/src/core/compiler/element_ref'; -import {NgIf} from 'angular2/src/directives/ng_if'; -import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; import {DOM} from 'angular2/src/dom/dom_adapter'; -import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; export function main() { describe('DynamicComponentLoader', function() { - describe("loading into existing location", () => { - function ijTestBed(fn: (ts: TestBed, async: AsyncTestCompleter) => void) { - return inject([TestBed, AsyncTestCompleter], fn); - } + describe("loading into a location", () => { + it('should work', + inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter], + (loader, tcb: TestComponentBuilder, async) => { + tcb.overrideView( + MyComp, + new viewAnn.View( + {template: '', directives: [Location]})) + .createAsync(MyComp) + .then((tc) => { - it('should work', ijTestBed((tb: TestBed, async) => { - tb.overrideView(MyComp, new viewAnn.View({ - template: '', - directives: [DynamicComp] - })); + loader.loadIntoLocation(DynamicallyLoaded, tc.elementRef, 'loc') + .then(ref => { + expect(tc.domElement).toHaveText("Location;DynamicallyLoaded;"); + async.done(); + }); + }); + })); - tb.createView(MyComp).then((view) => { - var dynamicComponent = view.rawView.locals.get("dynamic"); - expect(dynamicComponent).toBeAnInstanceOf(DynamicComp); + it('should return a disposable component ref', + inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter], + (loader, tcb: TestComponentBuilder, async) => { + tcb.overrideView( + MyComp, + new viewAnn.View( + {template: '', directives: [Location]})) + .createAsync(MyComp) + .then((tc) => { - dynamicComponent.done.then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('hello'); - async.done(); - }); - }); - })); + loader.loadIntoLocation(DynamicallyLoaded, tc.elementRef, 'loc') + .then(ref => { + ref.dispose(); + expect(tc.domElement).toHaveText("Location;"); + async.done(); + }); + }); + })); - it('should inject dependencies of the dynamically-loaded component', - ijTestBed((tb: TestBed, async) => { - tb.overrideView(MyComp, new viewAnn.View({ - template: '', - directives: [DynamicComp] - })); + it('should update host properties', + inject( + [DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter], + (loader, tcb: TestComponentBuilder, async) => { + tcb.overrideView( + MyComp, new viewAnn.View( + {template: '', directives: [Location]})) + .createAsync(MyComp) + .then((tc) => { + loader.loadIntoLocation(DynamicallyLoadedWithHostProps, tc.elementRef, 'loc') + .then(ref => { + ref.instance.id = "new value"; - tb.createView(MyComp).then((view) => { - var dynamicComponent = view.rawView.locals.get("dynamic"); - dynamicComponent.done.then((ref) => { - expect(ref.instance.dynamicallyCreatedComponentService) - .toBeAnInstanceOf(DynamicallyCreatedComponentService); - async.done(); - }); - }); - })); + tc.detectChanges(); - it('should allow destroying dynamically-loaded components', - inject([TestBed, AsyncTestCompleter], (tb, async) => { - tb.overrideView(MyComp, new viewAnn.View({ - template: '', - directives: [DynamicComp] - })); + var newlyInsertedElement = DOM.childNodes(tc.domElement)[1]; + expect(newlyInsertedElement.id) + .toEqual("new value") - tb.createView(MyComp).then((view) => { - var dynamicComponent = (view.rawView.locals).get("dynamic"); - dynamicComponent.done.then((ref) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText("hello"); + async.done(); + }); + }); + })); - ref.dispose(); - - expect(ref.instance.destroyed).toBe(true); - expect(view.rootNodes).toHaveText(""); - async.done(); - }); - }); - })); - - it('should allow to destroy and create them via viewcontainer directives', - ijTestBed((tb: TestBed, async) => { - tb.overrideView(MyComp, new viewAnn.View({ - template: - '
', - directives: [DynamicComp, NgIf] - })); - - tb.createView(MyComp).then((view) => { - view.context.ctxBoolProp = true; - view.detectChanges(); - var dynamicComponent = view.rawView.viewContainers[0].views[0].locals.get("dynamic"); - var promise = dynamicComponent.done.then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('hello'); - - view.context.ctxBoolProp = false; - view.detectChanges(); - - expect(view.rawView.viewContainers[0].views.length).toBe(0); - expect(view.rootNodes).toHaveText(''); - - view.context.ctxBoolProp = true; - view.detectChanges(); - - var dynamicComponent = view.rawView.viewContainers[0].views[0].locals.get("dynamic"); - return dynamicComponent.done; - }); - promise.then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('hello'); - async.done(); - }); - }); - })); + it('should throw if the variable does not exist', + inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter], + (loader, tcb: TestComponentBuilder, async) => { + tcb.overrideView( + MyComp, + new viewAnn.View( + {template: '', directives: [Location]})) + .createAsync(MyComp) + .then((tc) => { + expect(() => loader.loadIntoLocation(DynamicallyLoadedWithHostProps, + tc.elementRef, 'someUnknownVariable')) + .toThrowError('Could not find variable someUnknownVariable'); + async.done(); + }); + })); }); - describe("loading next to an existing location", () => { + describe("loading next to a location", () => { it('should work', inject([DynamicComponentLoader, TestBed, AsyncTestCompleter], (loader, tb: TestBed, async) => { @@ -140,7 +118,7 @@ export function main() { tb.createView(MyComp).then((view) => { var location = view.rawView.locals.get("loc"); - loader.loadNextToExistingLocation(DynamicallyLoaded, location.elementRef) + loader.loadNextToLocation(DynamicallyLoaded, location.elementRef) .then(ref => { expect(view.rootNodes).toHaveText("Location;DynamicallyLoaded;"); async.done(); @@ -158,9 +136,9 @@ export function main() { tb.createView(MyComp).then((view) => { var location = view.rawView.locals.get("loc"); - loader.loadNextToExistingLocation(DynamicallyLoaded, location.elementRef) + loader.loadNextToLocation(DynamicallyLoaded, location.elementRef) .then(ref => { - loader.loadNextToExistingLocation(DynamicallyLoaded2, location.elementRef) + loader.loadNextToLocation(DynamicallyLoaded2, location.elementRef) .then(ref2 => { expect(view.rootNodes) .toHaveText("Location;DynamicallyLoaded;DynamicallyLoaded2;") @@ -187,7 +165,7 @@ export function main() { tb.createView(MyComp).then((view) => { var location = view.rawView.locals.get("loc"); - loader.loadNextToExistingLocation(DynamicallyLoadedWithHostProps, location.elementRef) + loader.loadNextToLocation(DynamicallyLoadedWithHostProps, location.elementRef) .then(ref => { ref.instance.id = "new value"; @@ -203,38 +181,6 @@ export function main() { })); }); - describe('loading into a new location', () => { - it('should allow to create, update and destroy components', - inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => { - tb.overrideView(MyComp, new viewAnn.View({ - template: '', - directives: [ImperativeViewComponentUsingNgComponent] - })); - tb.createView(MyComp).then((view) => { - var userViewComponent = view.rawView.locals.get("impview"); - - userViewComponent.done.then((childComponentRef) => { - view.detectChanges(); - - expect(view.rootNodes).toHaveText('hello'); - - childComponentRef.instance.ctxProp = 'new'; - - view.detectChanges(); - - expect(view.rootNodes).toHaveText('new'); - - childComponentRef.dispose(); - - expect(view.rootNodes).toHaveText(''); - - async.done(); - }); - }); - })); - - }); - describe('loadAsRoot', () => { it('should allow to create, update and destroy components', inject([TestBed, AsyncTestCompleter, DynamicComponentLoader, DOCUMENT_TOKEN, Injector], @@ -270,25 +216,6 @@ export function main() { }); } -@Component({selector: 'imp-ng-cmp'}) -@View({template: ''}) -class ImperativeViewComponentUsingNgComponent { - done; - - constructor(self: ElementRef, dynamicComponentLoader: DynamicComponentLoader, - viewManager: AppViewManager, renderer: DomRenderer) { - var div = el('
'); - var shadowViewRef = viewManager.getComponentView(self); - renderer.setComponentViewRootNodes(shadowViewRef.render, [div]); - this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, null) - .then((componentRef) => { - var element = renderer.getRootNodes(componentRef.hostView.render)[0]; - DOM.appendChild(div, element); - return componentRef; - }); - } -} - @Component({ selector: 'child-cmp', }) @@ -301,15 +228,6 @@ class ChildComp { class DynamicallyCreatedComponentService {} -@Component({selector: 'dynamic-comp'}) -class DynamicComp { - done; - - constructor(loader: DynamicComponentLoader, location: ElementRef) { - this.done = loader.loadIntoExistingLocation(DynamicallyCreatedCmp, location); - } -} - @Component({ selector: 'hello-cmp', appInjector: [DynamicallyCreatedComponentService], diff --git a/modules/angular2/test/core/compiler/element_injector_spec.ts b/modules/angular2/test/core/compiler/element_injector_spec.ts index 14cf3996b9..70760cb613 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.ts +++ b/modules/angular2/test/core/compiler/element_injector_spec.ts @@ -671,7 +671,7 @@ export function main() { }); it("should instantiate directives that depend on pre built objects", () => { - var protoView = new AppProtoView(null, null, null); + var protoView = new AppProtoView(null, null, null, null); var bindings = ListWrapper.concat([NeedsProtoViewRef], extraBindings); var inj = injector(bindings, null, false, new PreBuiltObjects(null, null, protoView)); @@ -871,100 +871,6 @@ export function main() { })); }); - describe("dynamicallyCreateComponent", () => { - it("should create a component dynamically", () => { - var inj = injector(extraBindings); - - inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(SimpleDirective, null), - appInjector); - expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(SimpleDirective); - expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); - }); - - it("should inject parent dependencies into the dynamically-loaded component", () => { - var inj = parentChildInjectors(ListWrapper.concat([SimpleDirective], extraBindings), []); - inj.dynamicallyCreateComponent( - DirectiveBinding.createFromType(NeedsDirectiveFromAncestor, null), appInjector); - expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(NeedsDirectiveFromAncestor); - expect(inj.getDynamicallyLoadedComponent().dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it("should not inject the proxy component into the children of the dynamically-loaded component", - () => { - var injWithDynamicallyLoadedComponent = injector([SimpleDirective]); - injWithDynamicallyLoadedComponent.dynamicallyCreateComponent( - DirectiveBinding.createFromType(SomeOtherDirective, null), appInjector); - - var shadowDomProtoInjector = - createPei(null, 0, ListWrapper.concat([NeedsDirectiveFromAncestor], extraBindings)); - var shadowDomInj = shadowDomProtoInjector.instantiate(null); - - expect(() => shadowDomInj.hydrate(appInjector, injWithDynamicallyLoadedComponent, - defaultPreBuiltObjects)) - .toThrowError(containsRegexp(`No provider for ${stringify(SimpleDirective) }`)); - }); - - it("should not inject the dynamically-loaded component into directives on the same element", - () => { - var dynamicComp = - DirectiveBinding.createFromType(SomeOtherDirective, new dirAnn.Component()); - var proto = createPei( - null, 0, ListWrapper.concat([dynamicComp, NeedsDirective], extraBindings), 1, true); - var inj = proto.instantiate(null); - inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(SimpleDirective, null), - appInjector); - - expect(() => inj.hydrate(Injector.resolveAndCreate([]), null, null)) - .toThrowError( - `No provider for SimpleDirective! (${stringify(NeedsDirective) } -> ${stringify(SimpleDirective) })`); - }); - - it("should inject the dynamically-loaded component into the children of the dynamically-loaded component", - () => { - var componentDirective = DirectiveBinding.createFromType(SimpleDirective, null); - var injWithDynamicallyLoadedComponent = injector([]); - injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(componentDirective, - appInjector); - - var shadowDomProtoInjector = - createPei(null, 0, ListWrapper.concat([NeedsDirectiveFromAncestor], extraBindings)); - var shadowDomInjector = shadowDomProtoInjector.instantiate(null); - shadowDomInjector.hydrate(appInjector, injWithDynamicallyLoadedComponent, - defaultPreBuiltObjects); - - expect(shadowDomInjector.get(NeedsDirectiveFromAncestor)) - .toBeAnInstanceOf(NeedsDirectiveFromAncestor); - expect(shadowDomInjector.get(NeedsDirectiveFromAncestor).dependency) - .toBeAnInstanceOf(SimpleDirective); - }); - - it("should remove the dynamically-loaded component when dehydrating", () => { - var inj = injector(extraBindings); - inj.dynamicallyCreateComponent( - DirectiveBinding.createFromType(DirectiveWithDestroy, - new dirAnn.Directive({lifecycle: [onDestroy]})), - appInjector); - var dir = inj.getDynamicallyLoadedComponent(); - - inj.dehydrate(); - - expect(inj.getDynamicallyLoadedComponent()).toBe(null); - expect(dir.onDestroyCounter).toBe(1); - - inj.hydrate(null, null, null); - - expect(inj.getDynamicallyLoadedComponent()).toBe(null); - }); - - it("should inject services of the dynamically-loaded component", () => { - var inj = injector(extraBindings); - var appInjector = Injector.resolveAndCreate([bind("service").toValue("Service")]); - inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(NeedsService, null), - appInjector); - expect(inj.getDynamicallyLoadedComponent().service).toEqual("Service"); - }); - }); - describe('static attributes', () => { it('should be injectable', () => { var attributes = MapWrapper.create(); @@ -1016,7 +922,7 @@ export function main() { }); it("should inject ProtoViewRef", () => { - var protoView = new AppProtoView(null, null, null); + var protoView = new AppProtoView(null, null, null, null); var inj = injector(ListWrapper.concat([NeedsProtoViewRef], extraBindings), null, false, new PreBuiltObjects(null, null, protoView)); diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index c61dc956a6..7f3061c371 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -1090,6 +1090,16 @@ export function main() { }); })); + it('should report a meaningful error when a component is missing view annotation', + inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => { + PromiseWrapper.catchError(tb.createView(ComponentWithoutView, {context: ctx}), (e) => { + expect(e.message).toEqual( + `No View annotation found on component ${stringify(ComponentWithoutView)}`); + async.done(); + return null; + }); + })); + it('should report a meaningful error when a directive is null', inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => { @@ -1175,7 +1185,7 @@ export function main() { }); })); - it('should support free embedded views', + it('should support moving embedded views around', inject([TestBed, AsyncTestCompleter, ANCHOR_ELEMENT], (tb, async, anchorElement) => { tb.overrideView(MyComp, new viewAnn.View({ template: '
hello
', @@ -1730,20 +1740,19 @@ class ChildConsumingEventBus { class SomeImperativeViewport { view: ViewRef; anchor; - constructor(public element: ElementRef, public protoView: ProtoViewRef, - public viewManager: AppViewManager, public renderer: DomRenderer, - @Inject(ANCHOR_ELEMENT) anchor) { + constructor(public vc: ViewContainerRef, public protoView: ProtoViewRef, + public renderer: DomRenderer, @Inject(ANCHOR_ELEMENT) anchor) { this.view = null; this.anchor = anchor; } set someImpvp(value: boolean) { if (isPresent(this.view)) { - this.viewManager.destroyFreeEmbeddedView(this.element, this.view); + this.vc.clear(); this.view = null; } if (value) { - this.view = this.viewManager.createFreeEmbeddedView(this.element, this.protoView); + this.view = this.vc.create(this.protoView); var nodes = this.renderer.getRootNodes(this.view.render); for (var i = 0; i < nodes.length; i++) { DOM.appendChild(this.anchor, nodes[i]); @@ -1755,3 +1764,7 @@ class SomeImperativeViewport { @Directive({selector: '[export-dir]', exportAs: 'dir'}) class ExportDir { } + +@Component({selector: 'comp'}) +class ComponentWithoutView { +} diff --git a/modules/angular2/test/core/compiler/proto_view_factory_spec.ts b/modules/angular2/test/core/compiler/proto_view_factory_spec.ts index 9e72ebf6a3..aec9d94b6c 100644 --- a/modules/angular2/test/core/compiler/proto_view_factory_spec.ts +++ b/modules/angular2/test/core/compiler/proto_view_factory_spec.ts @@ -21,7 +21,8 @@ import {ChangeDetection, ChangeDetectorDefinition} from 'angular2/change_detecti import { ProtoViewFactory, getChangeDetectorDefinitions, - createDirectiveVariableBindings + createDirectiveVariableBindings, + createVariableLocations } from 'angular2/src/core/compiler/proto_view_factory'; import {Component, Directive} from 'angular2/annotations'; import {Key} from 'angular2/di'; @@ -139,6 +140,18 @@ export function main() { }).not.toThrow(); }); }); + + describe('createVariableLocations', () => { + it('should merge the names in the template for all ElementBinders', () => { + expect(createVariableLocations([ + new renderApi.ElementBinder( + {variableBindings: MapWrapper.createFromStringMap({"x": "a"})}), + new renderApi.ElementBinder( + {variableBindings: MapWrapper.createFromStringMap({"y": "b"})}) + + ])).toEqual(MapWrapper.createFromStringMap({'a': 0, 'b': 1})); + }); + }); }); } diff --git a/modules/angular2/test/core/compiler/view_container_ref_spec.ts b/modules/angular2/test/core/compiler/view_container_ref_spec.ts index e0fc388f4f..b1d7c88687 100644 --- a/modules/angular2/test/core/compiler/view_container_ref_spec.ts +++ b/modules/angular2/test/core/compiler/view_container_ref_spec.ts @@ -35,7 +35,7 @@ export function main() { function wrapView(view: AppView): ViewRef { return new ViewRef(view); } - function createProtoView() { return new AppProtoView(null, null, null); } + function createProtoView() { return new AppProtoView(null, null, null, null); } function createView() { return new AppView(null, createProtoView(), MapWrapper.create()); } diff --git a/modules/angular2/test/core/compiler/view_manager_spec.ts b/modules/angular2/test/core/compiler/view_manager_spec.ts index d72b5f3a2a..5a35be4b17 100644 --- a/modules/angular2/test/core/compiler/view_manager_spec.ts +++ b/modules/angular2/test/core/compiler/view_manager_spec.ts @@ -77,7 +77,7 @@ export function main() { staticChildComponentCount++; } } - var res = new AppProtoView(new MockProtoViewRef(staticChildComponentCount), null, null); + var res = new AppProtoView(new MockProtoViewRef(staticChildComponentCount), null, null, null); res.elementBinders = binders; return res; } @@ -155,139 +155,35 @@ export function main() { viewPool.spy('returnView').andReturn(true); }); - describe('createDynamicComponentView', () => { - - describe('basic functionality', () => { - var hostView, componentProtoView; - beforeEach(() => { - hostView = createView(createProtoView([createComponentElBinder(null)])); - componentProtoView = createProtoView(); - }); - - it('should create the view', () => { - expect(internalView(manager.createDynamicComponentView( - elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null))) - .toBe(createdViews[0]); - expect(createdViews[0].proto).toBe(componentProtoView); - expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]); - }); - - it('should get the view from the pool', () => { - var createdView; - viewPool.spy('getView').andCallFake((protoView) => { - createdView = createView(protoView); - return createdView; - }); - expect(internalView(manager.createDynamicComponentView( - elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null))) - .toBe(createdView); - expect(utils.spy('createView')).not.toHaveBeenCalled(); - expect(renderer.spy('createView')).not.toHaveBeenCalled(); - expect(viewListener.spy('viewCreated')).not.toHaveBeenCalled(); - }); - - it('should attach the view', () => { - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null) - expect(utils.spy('attachComponentView')) - .toHaveBeenCalledWith(hostView, 0, createdViews[0]); - expect(renderer.spy('attachComponentView')) - .toHaveBeenCalledWith(hostView.render, 0, createdViews[0].render); - }); - - it('should hydrate the dynamic component', () => { - var injector = new Injector([], null, false); - var componentBinding = bind(SomeComponent).toClass(SomeComponent); - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), componentBinding, - injector); - expect(utils.spy('hydrateDynamicComponentInElementInjector')) - .toHaveBeenCalledWith(hostView, 0, componentBinding, injector); - }); - - it('should hydrate the view', () => { - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null); - expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(hostView, 0); - expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); - }); - - it('should create and set the render view', () => { - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null); - expect(renderer.spy('createView')).toHaveBeenCalledWith(componentProtoView.render); - expect(createdViews[0].render).toBe(createdRenderViews[0]); - }); - - it('should set the event dispatcher', () => { - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null); - var cmpView = createdViews[0]; - expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView); - }); - }); - - describe('error cases', () => { - - it('should not allow to use non component indices', () => { - var hostView = createView(createProtoView([createEmptyElBinder()])); - var componentProtoView = createProtoView(); - expect(() => manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null)) - .toThrowError('There is no dynamic component directive at element 0'); - }); - - it('should not allow to use static component indices', () => { - var hostView = createView(createProtoView([createComponentElBinder(createProtoView())])); - var componentProtoView = createProtoView(); - expect(() => manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null)) - .toThrowError('There is no dynamic component directive at element 0'); - }); - - }); - - describe('recursively destroy dynamic child component views', () => { - // TODO - }); - - }); - describe('static child components', () => { describe('recursively create when not cached', () => { - var hostView, componentProtoView, nestedProtoView; + var rootProtoView, hostProtoView, componentProtoView, hostView, componentView; beforeEach(() => { - hostView = createView(createProtoView([createComponentElBinder(null)])); - nestedProtoView = createProtoView(); - componentProtoView = createProtoView([createComponentElBinder(nestedProtoView)]); + componentProtoView = createProtoView(); + hostProtoView = createProtoView([createComponentElBinder(componentProtoView)]); + rootProtoView = createProtoView([createComponentElBinder(hostProtoView)]); + manager.createRootHostView(wrapPv(rootProtoView), null, null); + hostView = createdViews[1]; + componentView = createdViews[2]; }); it('should create the view', () => { - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null); - expect(createdViews[0].proto).toBe(componentProtoView); - expect(createdViews[1].proto).toBe(nestedProtoView); + expect(hostView.proto).toBe(hostProtoView); + expect(componentView.proto).toBe(componentProtoView); }); it('should hydrate the view', () => { - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null); - expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(createdViews[0], 0); - expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); + expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(hostView, 0); + expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(componentView.render); }); - it('should set the render view', () => { - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null); - expect(createdViews[1].render).toBe(createdRenderViews[1]) - }); + it('should set the render view', + () => {expect(componentView.render).toBe(createdRenderViews[2])}); it('should set the event dispatcher', () => { - manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), - wrapPv(componentProtoView), null, null); - var cmpView = createdViews[1]; - expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView); + expect(renderer.spy('setEventDispatcher')) + .toHaveBeenCalledWith(componentView.render, componentView); }); }); @@ -302,200 +198,6 @@ export function main() { }); - describe('createFreeHostView', () => { - - // Note: We don't add tests for recursion or viewpool here as we assume that - // this is using the same mechanism as the other methods... - - describe('basic functionality', () => { - var parentHostView, parentView, hostProtoView; - beforeEach(() => { - parentHostView = createView(createProtoView([createComponentElBinder(null)])); - parentView = createView(); - utils.attachComponentView(parentHostView, 0, parentView); - hostProtoView = createProtoView([createComponentElBinder(null)]); - }); - - it('should create the view', () => { - expect(internalView(manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), - wrapPv(hostProtoView), null))) - .toBe(createdViews[0]); - expect(createdViews[0].proto).toBe(hostProtoView); - expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]); - }); - - it('should attachAndHydrate the view', () => { - var injector = new Injector([], null, false); - manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), - injector); - expect(utils.spy('attachAndHydrateFreeHostView')) - .toHaveBeenCalledWith(parentHostView, 0, createdViews[0], injector); - expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); - }); - - it('should create and set the render view', () => { - manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), - null); - expect(renderer.spy('createView')).toHaveBeenCalledWith(hostProtoView.render); - expect(createdViews[0].render).toBe(createdRenderViews[0]); - }); - - it('should set the event dispatcher', () => { - manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), - null); - var cmpView = createdViews[0]; - expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView); - }); - }); - - }); - - - describe('destroyFreeHostView', () => { - describe('basic functionality', () => { - var parentHostView, parentView, hostProtoView, hostView, hostRenderViewRef; - beforeEach(() => { - parentHostView = createView(createProtoView([createComponentElBinder(null)])); - parentView = createView(); - utils.attachComponentView(parentHostView, 0, parentView); - hostProtoView = createProtoView([createComponentElBinder(null)]); - hostView = internalView(manager.createFreeHostView( - elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null)); - hostRenderViewRef = hostView.render; - }); - - it('should detach', () => { - manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); - expect(utils.spy('detachFreeHostView')).toHaveBeenCalledWith(parentView, hostView); - }); - - it('should dehydrate', () => { - manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); - expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView); - expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render); - }); - - it('should detach the render view', () => { - manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); - expect(renderer.spy('detachFreeView')).toHaveBeenCalledWith(hostRenderViewRef); - }); - - it('should return the view to the pool', () => { - manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); - expect(viewPool.spy('returnView')).toHaveBeenCalledWith(hostView); - expect(renderer.spy('destroyView')).not.toHaveBeenCalled(); - }); - - it('should destroy the view if the pool is full', () => { - viewPool.spy('returnView').andReturn(false); - manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); - expect(renderer.spy('destroyView')).toHaveBeenCalledWith(hostView.render); - expect(viewListener.spy('viewDestroyed')).toHaveBeenCalledWith(hostView); - }); - - }); - - describe('recursively destroy inPlaceHostViews', () => { - // TODO - }); - - }); - - describe('createFreeEmbeddedView', () => { - - // Note: We don't add tests for recursion or viewpool here as we assume that - // this is using the same mechanism as the other methods... - - describe('basic functionality', () => { - var parentView, childProtoView; - beforeEach(() => { - parentView = createView(createProtoView([createEmptyElBinder()])); - childProtoView = createProtoView(); - }); - - it('should create the view', () => { - expect(internalView(manager.createFreeEmbeddedView(elementRef(wrapView(parentView), 0), - wrapPv(childProtoView), null))) - .toBe(createdViews[0]); - expect(createdViews[0].proto).toBe(childProtoView); - expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]); - }); - - it('should attachAndHydrate the view', () => { - var injector = new Injector([], null, false); - manager.createFreeEmbeddedView(elementRef(wrapView(parentView), 0), - wrapPv(childProtoView), injector); - expect(utils.spy('attachAndHydrateFreeEmbeddedView')) - .toHaveBeenCalledWith(parentView, 0, createdViews[0], injector); - expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); - }); - - it('should create and set the render view', () => { - manager.createFreeEmbeddedView(elementRef(wrapView(parentView), 0), - wrapPv(childProtoView), null); - expect(renderer.spy('createView')).toHaveBeenCalledWith(childProtoView.render); - expect(createdViews[0].render).toBe(createdRenderViews[0]); - }); - - it('should set the event dispatcher', () => { - manager.createFreeEmbeddedView(elementRef(wrapView(parentView), 0), - wrapPv(childProtoView), null); - var cmpView = createdViews[0]; - expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView); - }); - }); - - }); - - - describe('destroyFreeEmbeddedView', () => { - describe('basic functionality', () => { - var parentView, childProtoView, childView; - beforeEach(() => { - parentView = createView(createProtoView([createEmptyElBinder()])); - childProtoView = createProtoView(); - childView = internalView(manager.createFreeEmbeddedView( - elementRef(wrapView(parentView), 0), wrapPv(childProtoView), null)); - }); - - it('should detach', () => { - manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView)); - expect(utils.spy('detachFreeEmbeddedView')) - .toHaveBeenCalledWith(parentView, 0, childView); - }); - - it('should dehydrate', () => { - manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView)); - expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(childView); - expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render); - }); - - it('should detach the render view', () => { - manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView)); - expect(renderer.spy('detachFreeView')).toHaveBeenCalledWith(childView.render); - }); - - it('should return the view to the pool', () => { - manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView)); - expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView); - expect(renderer.spy('destroyView')).not.toHaveBeenCalled(); - }); - - it('should destroy the view if the pool is full', () => { - viewPool.spy('returnView').andReturn(false); - manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView)); - expect(renderer.spy('destroyView')).toHaveBeenCalledWith(childView.render); - expect(viewListener.spy('viewDestroyed')).toHaveBeenCalledWith(childView); - }); - - }); - - describe('recursively destroyFreeEmbeddedView', () => { - // TODO - }); - - }); - describe('createRootHostView', () => { var hostProtoView; @@ -698,7 +400,6 @@ export function main() { describe('detachViewInContainer', () => { }); - }); } diff --git a/modules/angular2/test/core/compiler/view_manager_utils_spec.ts b/modules/angular2/test/core/compiler/view_manager_utils_spec.ts index 71de3793f8..66254b4071 100644 --- a/modules/angular2/test/core/compiler/view_manager_utils_spec.ts +++ b/modules/angular2/test/core/compiler/view_manager_utils_spec.ts @@ -61,13 +61,14 @@ export function main() { if (isBlank(binders)) { binders = []; } - var res = new AppProtoView(null, null, null); + var res = new AppProtoView(null, null, null, null); res.elementBinders = binders; return res; } function createElementInjector() { var host = new SpyElementInjector(); + var appInjector = new SpyInjector(); return SpyObject.stub(new SpyElementInjector(), { 'isExportingComponent': false, @@ -75,8 +76,8 @@ export function main() { 'getEventEmitterAccessors': [], 'getHostActionAccessors': [], 'getComponent': null, - 'getDynamicallyLoadedComponent': null, - 'getHost': host + 'getHost': host, + 'getShadowDomAppInjector': appInjector }, {}); } @@ -99,21 +100,7 @@ export function main() { beforeEach(() => { directiveResolver = new DirectiveResolver(); - utils = new AppViewManagerUtils(directiveResolver); - }); - - describe('hydrateDynamicComponentInElementInjector', () => { - - it('should not allow to overwrite an existing component', () => { - var hostView = createView(createProtoView([createComponentElBinder(createProtoView())])); - var componentBinding = bind(SomeComponent).toClass(SomeComponent); - SpyObject.stub(hostView.elementInjectors[0], - {'getDynamicallyLoadedComponent': new SomeComponent()}); - expect(() => utils.hydrateDynamicComponentInElementInjector(hostView, 0, componentBinding, - null)) - .toThrowError('There already is a dynamic component loaded at element 0'); - }); - + utils = new AppViewManagerUtils(); }); describe("hydrateComponentView", () => { @@ -246,6 +233,16 @@ export function main() { childView.preBuiltObjects[0]); }); + it('should use the shadowDomAppInjector of the context elementInjector if there is no host', + () => { + createViews(); + parentView.elementInjectors[0].spy('getHost').andReturn(null); + utils.hydrateViewInContainer(parentView, 0, parentView, 0, 0, null); + expect(childView.rootElementInjectors[0].spy('hydrate')) + .toHaveBeenCalledWith(parentView.elementInjectors[0].getShadowDomAppInjector(), null, + childView.preBuiltObjects[0]); + }); + }); describe('hydrateRootHostView', () => { @@ -295,3 +292,10 @@ class SpyPreBuiltObjects extends SpyObject { constructor() { super(PreBuiltObjects); } noSuchMethod(m) { return super.noSuchMethod(m) } } + +@proxy +@IMPLEMENTS(Injector) +class SpyInjector extends SpyObject { + constructor() { super(Injector); } + noSuchMethod(m) { return super.noSuchMethod(m) } +} diff --git a/modules/angular2/test/core/compiler/view_pool_spec.ts b/modules/angular2/test/core/compiler/view_pool_spec.ts index 213961dd9a..2533434b31 100644 --- a/modules/angular2/test/core/compiler/view_pool_spec.ts +++ b/modules/angular2/test/core/compiler/view_pool_spec.ts @@ -24,7 +24,7 @@ export function main() { function createViewPool({capacity}): AppViewPool { return new AppViewPool(capacity); } - function createProtoView() { return new AppProtoView(null, null, null); } + function createProtoView() { return new AppProtoView(null, null, null, null); } function createView(pv) { return new AppView(null, pv, MapWrapper.create()); } diff --git a/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts b/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts index 0abe235635..8ab33112b7 100644 --- a/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts +++ b/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts @@ -40,20 +40,6 @@ export function main() { }); })); - it('should create and destroy free views', - inject([AsyncTestCompleter, DomTestbed], (async, tb) => { - tb.compiler.compileHost(someComponent) - .then((hostProtoViewDto) => { - var view = new TestView(tb.renderer.createView(hostProtoViewDto.render)); - var hostElement = tb.renderer.getRootNodes(view.viewRef)[0]; - DOM.appendChild(tb.rootEl, hostElement); - - tb.renderer.detachFreeView(view.viewRef); - expect(DOM.parentElement(hostElement)).toBeFalsy(); - async.done(); - }); - })); - it('should attach and detach component views', inject([AsyncTestCompleter, DomTestbed], (async, tb) => { tb.compileAll([ diff --git a/modules/angular2_material/src/components/dialog/dialog.ts b/modules/angular2_material/src/components/dialog/dialog.ts index 5a4d577da5..0eab97d633 100644 --- a/modules/angular2_material/src/components/dialog/dialog.ts +++ b/modules/angular2_material/src/components/dialog/dialog.ts @@ -59,7 +59,7 @@ export class MdDialog { var backdropRefPromise = this._openBackdrop(elementRef, contentInjector); // First, load the MdDialogContainer, into which the given component will be loaded. - return this.componentLoader.loadIntoNewLocation(MdDialogContainer, elementRef) + return this.componentLoader.loadNextToLocation(MdDialogContainer, elementRef) .then(containerRef => { // TODO(tbosch): clean this up when we have custom renderers // (https://github.com/angular/angular/issues/1807) @@ -86,8 +86,8 @@ export class MdDialog { dialogRef.containerRef = containerRef; // Now load the given component into the MdDialogContainer. - return this.componentLoader.loadNextToExistingLocation( - type, containerRef.instance.contentRef, contentInjector) + return this.componentLoader.loadNextToLocation(type, containerRef.instance.contentRef, + contentInjector) .then(contentRef => { // Wrap both component refs for the container and the content so that we can return @@ -107,7 +107,7 @@ export class MdDialog { /** Loads the dialog backdrop (transparent overlay over the rest of the page). */ _openBackdrop(elementRef: ElementRef, injector: Injector): Promise { - return this.componentLoader.loadIntoNewLocation(MdBackdrop, elementRef, injector) + return this.componentLoader.loadNextToLocation(MdBackdrop, elementRef, injector) .then((componentRef) => { // TODO(tbosch): clean this up when we have custom renderers // (https://github.com/angular/angular/issues/1807) diff --git a/modules/benchmarks/src/compiler/compiler_benchmark.ts b/modules/benchmarks/src/compiler/compiler_benchmark.ts index d27f3ec64e..7c115a624c 100644 --- a/modules/benchmarks/src/compiler/compiler_benchmark.ts +++ b/modules/benchmarks/src/compiler/compiler_benchmark.ts @@ -58,12 +58,12 @@ export function main() { function compileNoBindings() { cache.clear(); - return compiler.compile(BenchmarkComponentNoBindings); + return compiler.compileInHost(BenchmarkComponentNoBindings); } function compileWithBindings() { cache.clear(); - return compiler.compile(BenchmarkComponentWithBindings); + return compiler.compileInHost(BenchmarkComponentWithBindings); } bindAction('#compileNoBindings', measureWrapper(compileNoBindings, 'No Bindings')); @@ -122,7 +122,7 @@ class MultipleTemplateResolver extends TemplateResolver { } } -@Component() +@Component({selector: 'cmp-nobind'}) @View({ directives: [Dir0, Dir1, Dir2, Dir3, Dir4], template: ` @@ -140,7 +140,7 @@ class MultipleTemplateResolver extends TemplateResolver { class BenchmarkComponentNoBindings { } -@Component() +@Component({selector: 'cmp-withbind'}) @View({ directives: [Dir0, Dir1, Dir2, Dir3, Dir4], template: ` diff --git a/modules/benchmarks/src/costs/index.ts b/modules/benchmarks/src/costs/index.ts index 74ec569f2f..a39f98e306 100644 --- a/modules/benchmarks/src/costs/index.ts +++ b/modules/benchmarks/src/costs/index.ts @@ -61,10 +61,10 @@ class DummyComponent { class DummyDirective { } -@Component({selector: 'dynamic-dummy'}) +@Directive({selector: 'dynamic-dummy'}) class DynamicDummy { constructor(loader: DynamicComponentLoader, location: ElementRef) { - loader.loadIntoExistingLocation(DummyComponent, location); + loader.loadNextToLocation(DummyComponent, location); } }