diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index f97a574685..1755b6d707 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -28,8 +28,9 @@ import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_res import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner'; import {ComponentRef, DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability'; -import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory'; -import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator'; +import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; +import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; +import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {Renderer} from 'angular2/src/render/api'; import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; @@ -105,12 +106,13 @@ function _injectorBindings(appComponentType): List { ProtoViewFactory, // TODO(tbosch): We need an explicit factory here, as // we are getting errors in dart2js with mirrors... - bind(ViewFactory).toFactory( - (capacity, renderer) => new ViewFactory(capacity, renderer), - [VIEW_POOL_CAPACITY, Renderer] + bind(AppViewPool).toFactory( + (capacity) => new AppViewPool(capacity), + [APP_VIEW_POOL_CAPACITY] ), - bind(VIEW_POOL_CAPACITY).toValue(10000), - AppViewHydrator, + bind(APP_VIEW_POOL_CAPACITY).toValue(10000), + AppViewManager, + AppViewManagerUtils, Compiler, CompilerCache, TemplateResolver, diff --git a/modules/angular2/src/core/compiler/compiler.js b/modules/angular2/src/core/compiler/compiler.js index c6f50cd453..06f53b040c 100644 --- a/modules/angular2/src/core/compiler/compiler.js +++ b/modules/angular2/src/core/compiler/compiler.js @@ -88,13 +88,17 @@ export class Compiler { // Create a hostView as if the compiler encountered . // Used for bootstrapping. compileInHost(componentTypeOrBinding:any):Promise { + var componentBinding = this._bindDirective(componentTypeOrBinding); + this._assertTypeIsComponent(componentBinding); return this._renderer.createHostProtoView('host').then( (hostRenderPv) => { - return this._compileNestedProtoViews(null, hostRenderPv, [this._bindDirective(componentTypeOrBinding)], true); + return this._compileNestedProtoViews(null, hostRenderPv, [componentBinding], true); }); } compile(component: Type):Promise { - var protoView = this._compile(this._bindDirective(component)); + var componentBinding = this._bindDirective(component); + this._assertTypeIsComponent(componentBinding); + var protoView = this._compile(componentBinding); return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView); } @@ -265,4 +269,9 @@ export class Compiler { } } + _assertTypeIsComponent(directiveBinding:DirectiveBinding):void { + if (!(directiveBinding.annotation instanceof Component)) { + throw new BaseException(`Could not load '${stringify(directiveBinding.key.token)}' because it is not a component.`); + } + } } diff --git a/modules/angular2/src/core/compiler/dynamic_component_loader.js b/modules/angular2/src/core/compiler/dynamic_component_loader.js index fc65823a41..1195748b7b 100644 --- a/modules/angular2/src/core/compiler/dynamic_component_loader.js +++ b/modules/angular2/src/core/compiler/dynamic_component_loader.js @@ -1,12 +1,9 @@ -import {Key, Injector, Injectable, ResolvedBinding} from 'angular2/di' +import {Key, Injector, Injectable, ResolvedBinding, Binding, bind} from 'angular2/di' import {Compiler} from './compiler'; -import {DirectiveMetadataReader} from './directive_metadata_reader'; import {Type, BaseException, stringify, isPresent} from 'angular2/src/facade/lang'; import {Promise} from 'angular2/src/facade/async'; -import {Component} from 'angular2/src/core/annotations/annotations'; -import {ViewFactory} from 'angular2/src/core/compiler/view_factory'; -import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator'; -import {ElementRef, DirectiveBinding} from './element_injector'; +import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; +import {ElementRef} from './element_injector'; import {AppView} from './view'; /** @@ -47,31 +44,23 @@ export class ComponentRef { @Injectable() export class DynamicComponentLoader { _compiler:Compiler; - _viewFactory:ViewFactory; - _viewHydrator:AppViewHydrator; - _directiveMetadataReader:DirectiveMetadataReader; + _viewManager:AppViewManager; - constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader, - viewFactory:ViewFactory, viewHydrator:AppViewHydrator) { + constructor(compiler:Compiler, + viewManager: AppViewManager) { this._compiler = compiler; - this._directiveMetadataReader = directiveMetadataReader; - this._viewFactory = viewFactory; - this._viewHydrator = viewHydrator; + this._viewManager = viewManager; } /** * 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(type:Type, location:ElementRef, injector:Injector = null):Promise { - this._assertTypeIsComponent(type); - var annotation = this._directiveMetadataReader.read(type).annotation; - var componentBinding = DirectiveBinding.createFromType(type, annotation); - - return this._compiler.compile(type).then(componentProtoView => { - var componentView = this._viewFactory.getView(componentProtoView); - this._viewHydrator.hydrateDynamicComponentView( - location, componentView, componentBinding, injector); + loadIntoExistingLocation(typeOrBinding, location:ElementRef, injector:Injector = null):Promise { + var binding = this._getBinding(typeOrBinding); + return this._compiler.compile(binding.token).then(componentProtoView => { + var componentView = this._viewManager.createDynamicComponentView( + location, componentProtoView, binding, injector); var dispose = () => {throw new BaseException("Not implemented");}; return new ComponentRef(location, location.elementInjector.getDynamicallyLoadedComponent(), componentView, dispose); @@ -82,21 +71,16 @@ export class DynamicComponentLoader { * Loads a component in the element specified by elementOrSelector. The loaded component receives * injection normally as a hosted view. */ - loadIntoNewLocation(type:Type, parentComponentLocation:ElementRef, elementOrSelector:any, + loadIntoNewLocation(typeOrBinding, parentComponentLocation:ElementRef, elementOrSelector:any, injector:Injector = null):Promise { - this._assertTypeIsComponent(type); - - return this._compiler.compileInHost(type).then(hostProtoView => { - var hostView = this._viewFactory.getView(hostProtoView); - this._viewHydrator.hydrateInPlaceHostView( - parentComponentLocation, elementOrSelector, hostView, injector - ); + return this._compiler.compileInHost(this._getBinding(typeOrBinding)).then(hostProtoView => { + var hostView = this._viewManager.createInPlaceHostView( + parentComponentLocation, elementOrSelector, hostProtoView, injector); var newLocation = hostView.elementInjectors[0].getElementRef(); var component = hostView.elementInjectors[0].getComponent(); var dispose = () => { - this._viewHydrator.dehydrateInPlaceHostView(parentComponentLocation, hostView); - this._viewFactory.returnView(hostView); + this._viewManager.destroyInPlaceHostView(parentComponentLocation, hostView); }; return new ComponentRef(newLocation, component, hostView.componentChildViews[0], dispose); }); @@ -106,10 +90,9 @@ export class DynamicComponentLoader { * Loads a component next to the provided ElementRef. The loaded component receives * injection normally as a hosted view. */ - loadNextToExistingLocation(type:Type, location:ElementRef, injector:Injector = null):Promise { - this._assertTypeIsComponent(type); - - return this._compiler.compileInHost(type).then(hostProtoView => { + loadNextToExistingLocation(typeOrBinding, location:ElementRef, injector:Injector = null):Promise { + var binding = this._getBinding(typeOrBinding); + return this._compiler.compileInHost(binding).then(hostProtoView => { var hostView = location.viewContainer.create(-1, hostProtoView, injector); var newLocation = hostView.elementInjectors[0].getElementRef(); @@ -122,11 +105,14 @@ export class DynamicComponentLoader { }); } - /** Asserts that the type being dynamically instantiated is a Component. */ - _assertTypeIsComponent(type:Type):void { - var annotation = this._directiveMetadataReader.read(type).annotation; - if (!(annotation instanceof Component)) { - throw new BaseException(`Could not load '${stringify(type)}' because it is not a component.`); + _getBinding(typeOrBinding) { + var binding; + if (typeOrBinding instanceof Binding) { + binding = typeOrBinding; + } else { + binding = bind(typeOrBinding).toClass(typeOrBinding); } + return binding; } + } diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index 0f86e70684..b1b1d93bba 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -7,6 +7,7 @@ import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingErro import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; import {Attribute, Query} from 'angular2/src/core/annotations/di'; import * as viewModule from 'angular2/src/core/compiler/view'; +import * as avmModule from './view_manager'; import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {Directive, Component, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations'; @@ -30,28 +31,30 @@ export class ElementRef { boundElementIndex:number; injector:Injector; elementInjector:ElementInjector; + viewContainer:ViewContainer; - constructor(elementInjector, hostView, boundElementIndex, injector){ + constructor(elementInjector, hostView, boundElementIndex, injector, viewManager, defaultProtoView){ this.elementInjector = elementInjector; this.hostView = hostView; this.boundElementIndex = boundElementIndex; this.injector = injector; - } - - get viewContainer() { - return this.hostView.getOrCreateViewContainer(this.boundElementIndex); + this.viewContainer = new ViewContainer(viewManager, this, defaultProtoView); } } class StaticKeys { + viewManagerId:number; viewId:number; ngElementId:number; + defaultProtoViewId:number; viewContainerId:number; changeDetectorRefId:number; elementRefId:number; constructor() { //TODO: vsavkin Key.annotate(Key.get(AppView), 'static') + this.viewManagerId = Key.get(avmModule.AppViewManager).id; + this.defaultProtoViewId = Key.get(viewModule.AppProtoView).id; this.viewId = Key.get(viewModule.AppView).id; this.ngElementId = Key.get(NgElement).id; this.viewContainerId = Key.get(ViewContainer).id; @@ -290,13 +293,15 @@ export class DirectiveBinding extends ResolvedBinding { // TODO(rado): benchmark and consider rolling in as ElementInjector fields. export class PreBuiltObjects { + viewManager:avmModule.AppViewManager; + defaultProtoView:viewModule.AppProtoView; view:viewModule.AppView; element:NgElement; - changeDetector:ChangeDetector; - constructor(view, element:NgElement, changeDetector:ChangeDetector) { + constructor(viewManager:avmModule.AppViewManager, view:viewModule.AppView, element:NgElement, defaultProtoView:viewModule.AppProtoView) { + this.viewManager = viewManager; this.view = view; + this.defaultProtoView = defaultProtoView; this.element = element; - this.changeDetector = changeDetector; } } @@ -649,10 +654,6 @@ export class ElementInjector extends TreeNode { return this._preBuiltObjects.element; } - getChangeDetector() { - return this._preBuiltObjects.changeDetector; - } - getComponent() { if (this._proto._binding0IsComponent) { return this._obj0; @@ -662,7 +663,8 @@ export class ElementInjector extends TreeNode { } getElementRef() { - return new ElementRef(this, this._preBuiltObjects.view, this._proto.index, this._lightDomAppInjector); + return new ElementRef(this, this._preBuiltObjects.view, this._proto.index, this._lightDomAppInjector, + this._preBuiltObjects.viewManager, this._preBuiltObjects.defaultProtoView); } getDynamicallyLoadedComponent() { @@ -732,9 +734,16 @@ export class ElementInjector extends TreeNode { _getByDependency(dep:DirectiveDependency, requestor:Key) { if (isPresent(dep.attributeName)) return this._buildAttribute(dep); if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list; + if (dep.key.id === StaticKeys.instance().changeDetectorRefId) { + var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index]; + return componentView.changeDetector.ref; + } if (dep.key.id === StaticKeys.instance().elementRefId) { return this.getElementRef(); } + if (dep.key.id === StaticKeys.instance().viewContainerId) { + return this.getElementRef().viewContainer; + } return this._getByKey(dep.key, dep.depth, dep.optional, requestor); } @@ -906,10 +915,10 @@ export class ElementInjector extends TreeNode { _getPreBuiltObjectByKeyId(keyId:int) { var staticKeys = StaticKeys.instance(); // TODO: AppView should not be injectable. Remove it. + if (keyId === staticKeys.viewManagerId) return this._preBuiltObjects.viewManagerId; if (keyId === staticKeys.viewId) return this._preBuiltObjects.view; if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element; - if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.view.getOrCreateViewContainer(this._proto.index); - if (keyId === staticKeys.changeDetectorRefId) return this._preBuiltObjects.changeDetector.ref; + if (keyId === staticKeys.defaultProtoViewId) return this._preBuiltObjects.defaultProtoView; //TODO add other objects as needed return _undefined; @@ -968,6 +977,10 @@ export class ElementInjector extends TreeNode { return this._lightDomAppInjector; } + getShadowDomAppInjector() { + return this._shadowDomAppInjector; + } + getHost() { return this._host; } diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index e0bf399292..3ae4705bd8 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -6,10 +6,18 @@ import {ProtoElementInjector, ElementInjector, PreBuiltObjects, DirectiveBinding import {ElementBinder} from './element_binder'; import {SetterFn} from 'angular2/src/reflection/types'; import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; -import {ViewContainer} from './view_container'; import * as renderApi from 'angular2/src/render/api'; -import * as vfModule from './view_factory'; -import * as vhModule from './view_hydrator'; + +// TODO(tbosch): rename ViewContainer -> ViewContainerRef +// and InternalAppViewContainer -> ViewContainer! +export class InternalAppViewContainer { + views: List; + + constructor() { + // The order in this list matches the DOM order. + this.views = []; + } +} /** * Const of making objects: http://jsperf.com/instantiate-size-of-object @@ -28,12 +36,10 @@ export class AppView { /// Host views that were added by an imperative view. /// This is a dynamically growing / shrinking array. imperativeHostViews: List; - viewContainers: List; + viewContainers: List; preBuiltObjects: List; proto: AppProtoView; renderer: renderApi.Renderer; - viewFactory: vfModule.ViewFactory; - viewHydrator: vhModule.AppViewHydrator; /** * The context against which data-binding expressions in this view are evaluated against. @@ -49,7 +55,7 @@ export class AppView { */ locals:Locals; - constructor(renderer:renderApi.Renderer, viewFactory:vfModule.ViewFactory, proto:AppProtoView, protoLocals:Map) { + constructor(renderer:renderApi.Renderer, proto:AppProtoView, protoLocals:Map) { this.render = null; this.proto = proto; this.changeDetector = null; @@ -61,8 +67,6 @@ export class AppView { this.context = null; this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this this.renderer = renderer; - this.viewFactory = viewFactory; - this.viewHydrator = null; this.imperativeHostViews = []; } @@ -75,15 +79,6 @@ export class AppView { this.componentChildViews = componentChildViews; } - getOrCreateViewContainer(boundElementIndex:number):ViewContainer { - var viewContainer = this.viewContainers[boundElementIndex]; - if (isBlank(viewContainer)) { - viewContainer = new ViewContainer(this, this.proto.elementBinders[boundElementIndex].nestedProtoView, this.elementInjectors[boundElementIndex]); - this.viewContainers[boundElementIndex] = viewContainer; - } - return viewContainer; - } - setLocal(contextName: string, value):void { if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.'); if (!MapWrapper.contains(this.proto.variableBindings, contextName)) { @@ -130,8 +125,8 @@ export class AppView { } getDetectorFor(directive:DirectiveIndex) { - var elementInjector = this.elementInjectors[directive.elementIndex]; - return elementInjector.getChangeDetector(); + var childView = this.componentChildViews[directive.elementIndex]; + return isPresent(childView) ? childView.changeDetector : null; } // implementation of EventDispatcher#dispatchEvent diff --git a/modules/angular2/src/core/compiler/view_container.js b/modules/angular2/src/core/compiler/view_container.js index 6a3edcbd77..659f0d165c 100644 --- a/modules/angular2/src/core/compiler/view_container.js +++ b/modules/angular2/src/core/compiler/view_container.js @@ -1,108 +1,68 @@ import {ListWrapper, MapWrapper, List} from 'angular2/src/facade/collection'; -import {BaseException} from 'angular2/src/facade/lang'; import {Injector} from 'angular2/di'; import * as eiModule from 'angular2/src/core/compiler/element_injector'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; import * as viewModule from './view'; -import {ViewContainerRef} from 'angular2/src/render/api'; +import * as avmModule from './view_manager'; /** * @exportedAs angular2/view */ export class ViewContainer { - parentView: viewModule.AppView; - defaultProtoView: viewModule.AppProtoView; - _views: List; - elementInjector: eiModule.ElementInjector; + _viewManager: avmModule.AppViewManager; + _location: eiModule.ElementRef; + _defaultProtoView: viewModule.AppProtoView; - constructor(parentView: viewModule.AppView, - defaultProtoView: viewModule.AppProtoView, - elementInjector: eiModule.ElementInjector) { - this.parentView = parentView; - this.defaultProtoView = defaultProtoView; - this.elementInjector = elementInjector; - - // The order in this list matches the DOM order. - this._views = []; + constructor(viewManager: avmModule.AppViewManager, + location: eiModule.ElementRef, + defaultProtoView: viewModule.AppProtoView) { + this._viewManager = viewManager; + this._location = location; + this._defaultProtoView = defaultProtoView; } - getRender():ViewContainerRef { - return new ViewContainerRef(this.parentView.render, this.elementInjector.getBoundElementIndex()); - } - - internalClearWithoutRender():void { - for (var i = this._views.length - 1; i >= 0; i--) { - this._detachInjectors(i); - } + _getViews() { + var vc = this._location.hostView.viewContainers[this._location.boundElementIndex]; + return isPresent(vc) ? vc.views : []; } clear():void { - for (var i = this._views.length - 1; i >= 0; i--) { + for (var i = this.length - 1; i >= 0; i--) { this.remove(i); } } get(index: number): viewModule.AppView { - return this._views[index]; + return this._getViews()[index]; } get length() /* :int */ { - return this._views.length; - } - - _siblingInjectorToLinkAfter(index: number):eiModule.ElementInjector { - if (index == 0) return null; - return ListWrapper.last(this._views[index - 1].rootElementInjectors) - } - - hydrated():boolean { - return this.parentView.hydrated(); + return this._getViews().length; } // TODO(rado): profile and decide whether bounds checks should be added // to the methods below. create(atIndex:number=-1, protoView:viewModule.AppProtoView = null, injector:Injector = null): viewModule.AppView { - if (atIndex == -1) atIndex = this._views.length; - if (!this.hydrated()) throw new BaseException( - 'Cannot create views on a dehydrated ViewContainer'); + if (atIndex == -1) atIndex = this.length; if (isBlank(protoView)) { - protoView = this.defaultProtoView; + protoView = this._defaultProtoView; } - var newView = this.parentView.viewFactory.getView(protoView); - // insertion must come before hydration so that element injector trees are attached. - this._insertInjectors(newView, atIndex); - this.parentView.viewHydrator.hydrateViewInViewContainer(this, atIndex, newView, injector); - - return newView; + return this._viewManager.createViewInContainer(this._location, atIndex, protoView, injector); } insert(view:viewModule.AppView, atIndex:number=-1): viewModule.AppView { - if (atIndex == -1) atIndex = this._views.length; - this._insertInjectors(view, atIndex); - this.parentView.changeDetector.addChild(view.changeDetector); - this.parentView.renderer.insertViewIntoContainer(this.getRender(), atIndex, view.render); - return view; - } - - _insertInjectors(view:viewModule.AppView, atIndex:number): viewModule.AppView { - ListWrapper.insert(this._views, atIndex, view); - this._linkElementInjectors(this._siblingInjectorToLinkAfter(atIndex), view); - - return view; + if (atIndex == -1) atIndex = this.length; + return this._viewManager.attachViewInContainer(this._location, atIndex, view); } indexOf(view:viewModule.AppView) { - return ListWrapper.indexOf(this._views, view); + return ListWrapper.indexOf(this._getViews(), view); } remove(atIndex:number=-1):void { - if (atIndex == -1) atIndex = this._views.length - 1; - var view = this._views[atIndex]; - // opposite order as in create - this.parentView.viewHydrator.dehydrateViewInViewContainer(this, atIndex, view); - this._detachInjectors(atIndex); - this.parentView.viewFactory.returnView(view); + if (atIndex == -1) atIndex = this.length - 1; + this._viewManager.destroyViewInContainer(this._location, atIndex); // view is intentionally not returned to the client. } @@ -111,29 +71,7 @@ export class ViewContainer { * moving the dom nodes while the directives in the view stay intact. */ detach(atIndex:number=-1): viewModule.AppView { - if (atIndex == -1) atIndex = this._views.length - 1; - var detachedView = this._detachInjectors(atIndex); - detachedView.changeDetector.remove(); - this.parentView.renderer.detachViewFromContainer(this.getRender(), atIndex); - return detachedView; - } - - _detachInjectors(atIndex:number): viewModule.AppView { - var detachedView = this.get(atIndex); - ListWrapper.removeAt(this._views, atIndex); - this._unlinkElementInjectors(detachedView); - return detachedView; - } - - _linkElementInjectors(sibling, view:viewModule.AppView):void { - for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) { - view.rootElementInjectors[i].linkAfter(this.elementInjector, sibling); - } - } - - _unlinkElementInjectors(view:viewModule.AppView):void { - for (var i = 0; i < view.rootElementInjectors.length; ++i) { - view.rootElementInjectors[i].unlink(); - } + if (atIndex == -1) atIndex = this.length - 1; + return this._viewManager.detachViewInContainer(this._location, atIndex); } } diff --git a/modules/angular2/src/core/compiler/view_factory.js b/modules/angular2/src/core/compiler/view_factory.js deleted file mode 100644 index 0f43020436..0000000000 --- a/modules/angular2/src/core/compiler/view_factory.js +++ /dev/null @@ -1,97 +0,0 @@ -import {Injectable, Inject, OpaqueToken} from 'angular2/di'; -import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; -import * as eli from './element_injector'; -import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; -import {NgElement} from 'angular2/src/core/compiler/ng_element'; -import * as viewModule from './view'; -import {Renderer} from 'angular2/src/render/api'; - -// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this! -export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity'; - -@Injectable() -export class ViewFactory { - _poolCapacityPerProtoView:number; - _pooledViewsPerProtoView:Map>; - _renderer:Renderer; - - constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, renderer:Renderer) { - this._poolCapacityPerProtoView = poolCapacityPerProtoView; - this._pooledViewsPerProtoView = MapWrapper.create(); - this._renderer = renderer; - } - - getView(protoView:viewModule.AppProtoView):viewModule.AppView { - var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView); - if (isPresent(pooledViews) && pooledViews.length > 0) { - return ListWrapper.removeLast(pooledViews); - } - return this._createView(protoView); - } - - returnView(view:viewModule.AppView) { - if (view.hydrated()) { - throw new BaseException('Only dehydrated Views can be put back into the pool!'); - } - var protoView = view.proto; - var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView); - if (isBlank(pooledViews)) { - pooledViews = []; - MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews); - } - if (pooledViews.length < this._poolCapacityPerProtoView) { - ListWrapper.push(pooledViews, view); - } - } - - _createView(protoView:viewModule.AppProtoView): viewModule.AppView { - var view = new viewModule.AppView(this._renderer, this, protoView, protoView.protoLocals); - var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindings, - protoView.getVariableBindings(), protoView.getdirectiveRecords()); - - var binders = protoView.elementBinders; - var elementInjectors = ListWrapper.createFixedSize(binders.length); - var rootElementInjectors = []; - var preBuiltObjects = ListWrapper.createFixedSize(binders.length); - var componentChildViews = ListWrapper.createFixedSize(binders.length); - - for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { - var binder = binders[binderIdx]; - var elementInjector = null; - - // elementInjectors and rootElementInjectors - var protoElementInjector = binder.protoElementInjector; - if (isPresent(protoElementInjector)) { - if (isPresent(protoElementInjector.parent)) { - var parentElementInjector = elementInjectors[protoElementInjector.parent.index]; - elementInjector = protoElementInjector.instantiate(parentElementInjector); - } else { - elementInjector = protoElementInjector.instantiate(null); - ListWrapper.push(rootElementInjectors, elementInjector); - } - } - elementInjectors[binderIdx] = elementInjector; - - // componentChildViews - var childChangeDetector = null; - if (binder.hasStaticComponent()) { - var childView = this._createView(binder.nestedProtoView); - childChangeDetector = childView.changeDetector; - changeDetector.addShadowDomChild(childChangeDetector); - - componentChildViews[binderIdx] = childView; - } - - // preBuiltObjects - if (isPresent(elementInjector)) { - preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(view, new NgElement(view, binderIdx), childChangeDetector); - } - } - - view.init(changeDetector, elementInjectors, rootElementInjectors, - preBuiltObjects, componentChildViews); - - return view; - } - -} diff --git a/modules/angular2/src/core/compiler/view_hydrator.js b/modules/angular2/src/core/compiler/view_hydrator.js deleted file mode 100644 index 2e180c51a6..0000000000 --- a/modules/angular2/src/core/compiler/view_hydrator.js +++ /dev/null @@ -1,302 +0,0 @@ -import {Injectable, Inject, OpaqueToken, Injector} from 'angular2/di'; -import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; -import * as eli from './element_injector'; -import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; -import * as vcModule from './view_container'; -import * as viewModule from './view'; -import {BindingPropagationConfig, Locals} from 'angular2/change_detection'; - -import * as renderApi from 'angular2/src/render/api'; -import {ViewFactory} from 'angular2/src/core/compiler/view_factory'; - -/** - * A dehydrated view is a state of the view that allows it to be moved around - * the view tree, without incurring the cost of recreating the underlying - * injectors and watch records. - * - * A dehydrated view has the following properties: - * - * - all element injectors are empty. - * - all appInjectors are released. - * - all viewcontainers are empty. - * - all context locals are set to null. - * - the view context is null. - * - * A call to hydrate/dehydrate is called whenever a view is attached/detached, - * but it does not do the attach/detach itself. - */ -@Injectable() -export class AppViewHydrator { - _renderer:renderApi.Renderer; - _viewFactory:ViewFactory; - - constructor(renderer:renderApi.Renderer, viewFactory:ViewFactory) { - this._renderer = renderer; - this._viewFactory = viewFactory; - } - - hydrateDynamicComponentView(location:eli.ElementRef, - componentView:viewModule.AppView, componentDirective:eli.DirectiveBinding, injector:Injector) { - var hostView = location.hostView; - var boundElementIndex = location.boundElementIndex; - var binder = hostView.proto.elementBinders[boundElementIndex]; - if (!binder.hasDynamicComponent()) { - throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`); - } - if (isPresent(hostView.componentChildViews[boundElementIndex])) { - throw new BaseException(`There already is a bound component at element ${boundElementIndex}`); - } - var hostElementInjector = hostView.elementInjectors[boundElementIndex]; - if (isBlank(injector)) { - injector = hostElementInjector.getLightDomAppInjector(); - } - - // shadowDomAppInjector - var shadowDomAppInjector = this._createShadowDomAppInjector(componentDirective, injector); - // Needed to make rtts-assert happy in unit tests... - if (isBlank(shadowDomAppInjector)) { - shadowDomAppInjector = null; - } - // create component instance - var component = hostElementInjector.dynamicallyCreateComponent(componentDirective, shadowDomAppInjector); - - // componentView - hostView.componentChildViews[boundElementIndex] = componentView; - hostView.changeDetector.addShadowDomChild(componentView.changeDetector); - - // render views - var renderViewRefs = this._renderer.createDynamicComponentView(hostView.render, boundElementIndex, componentView.proto.render); - - this._viewHydrateRecurse( - componentView, renderViewRefs, 0, shadowDomAppInjector, hostElementInjector, component, null - ); - } - - dehydrateDynamicComponentView(parentView:viewModule.AppView, boundElementIndex:number) { - throw new BaseException('Not yet implemented!'); - // Something along these lines: - // var binder = parentView.proto.elementBinders[boundElementIndex]; - // if (!binder.hasDynamicComponent()) { - // throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`); - // } - // var componentView = parentView.componentChildViews[boundElementIndex]; - // if (isBlank(componentView)) { - // throw new BaseException(`There is no bound component at element ${boundElementIndex}`); - // } - // this._viewDehydrateRecurse(componentView); - // parentView.changeDetector.removeShadowDomChild(componentView.changeDetector); - // this._renderer.destroyDynamicComponentChildView(parentView.render, boundElementIndex); - // parentView.componentChildViews[boundElementIndex] = null; - } - - hydrateInPlaceHostView(parentComponentLocation:eli.ElementRef, - hostElementSelector, hostView:viewModule.AppView, injector:Injector) { - var parentRenderViewRef = null; - if (isPresent(parentComponentLocation)) { - var parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex]; - parentRenderViewRef = parentView.render; - parentView.changeDetector.addChild(hostView.changeDetector); - ListWrapper.push(parentView.imperativeHostViews, hostView); - - if (isBlank(injector)) { - injector = parentComponentLocation.injector; - } - } - - var binder = hostView.proto.elementBinders[0]; - var shadowDomAppInjector = this._createShadowDomAppInjector(binder.componentDirective, injector); - - var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostView.proto.render); - - this._viewHydrateRecurse( - hostView, renderViewRefs, 0, shadowDomAppInjector, null, new Object(), null - ); - } - - dehydrateInPlaceHostView(parentComponentLocation:eli.ElementRef, hostView:viewModule.AppView) { - var parentRenderViewRef = null; - if (isPresent(parentComponentLocation)) { - var parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex]; - parentRenderViewRef = parentView.render; - ListWrapper.remove(parentView.imperativeHostViews, hostView); - parentView.changeDetector.removeChild(hostView.changeDetector); - } - var render = hostView.render; - this._viewDehydrateRecurse(hostView); - this._renderer.destroyInPlaceHostView(parentRenderViewRef, render); - } - - hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView, injector:Injector = null) { - if (!viewContainer.hydrated()) throw new BaseException( - 'Cannot create views on a dehydrated ViewContainer'); - if (isBlank(injector)) { - injector = viewContainer.elementInjector.getLightDomAppInjector(); - } - var renderViewRefs = this._renderer.createViewInContainer(viewContainer.getRender(), atIndex, view.proto.render); - viewContainer.parentView.changeDetector.addChild(view.changeDetector); - this._viewHydrateRecurse(view, renderViewRefs, 0, injector, viewContainer.elementInjector.getHost(), - viewContainer.parentView.context, viewContainer.parentView.locals); - } - - dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView) { - view.changeDetector.remove(); - this._viewDehydrateRecurse(view); - this._renderer.destroyViewInContainer(viewContainer.getRender(), atIndex); - } - - _viewHydrateRecurse( - view:viewModule.AppView, - renderComponentViewRefs:List, - renderComponentIndex:number, - appInjector: Injector, hostElementInjector: eli.ElementInjector, - context: Object, locals:Locals):number { - if (view.hydrated()) throw new BaseException('The view is already hydrated.'); - view.viewHydrator = this; - view.render = renderComponentViewRefs[renderComponentIndex++]; - - view.context = context; - view.locals.parent = locals; - - var binders = view.proto.elementBinders; - for (var i = 0; i < binders.length; ++i) { - var componentDirective = binders[i].componentDirective; - var shadowDomAppInjector = null; - - // shadowDomAppInjector - if (isPresent(componentDirective)) { - shadowDomAppInjector = this._createShadowDomAppInjector(componentDirective, appInjector); - } else { - shadowDomAppInjector = null; - } - - // elementInjectors - var elementInjector = view.elementInjectors[i]; - if (isPresent(elementInjector)) { - elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, view.preBuiltObjects[i]); - this._setUpEventEmitters(view, elementInjector, i); - - // The exporting of $implicit is a special case. Since multiple elements will all export - // the different values as $implicit, directly assign $implicit bindings to the variable - // name. - var exportImplicitName = elementInjector.getExportImplicitName(); - if (elementInjector.isExportingComponent()) { - view.locals.set(exportImplicitName, elementInjector.getComponent()); - } else if (elementInjector.isExportingElement()) { - view.locals.set(exportImplicitName, elementInjector.getNgElement().domElement); - } - } - - if (binders[i].hasStaticComponent()) { - renderComponentIndex = this._viewHydrateRecurse( - view.componentChildViews[i], - renderComponentViewRefs, - renderComponentIndex, - shadowDomAppInjector, - elementInjector, - elementInjector.getComponent(), - null - ); - } - } - view.changeDetector.hydrate(view.context, view.locals, view); - view.renderer.setEventDispatcher(view.render, view); - return renderComponentIndex; - } - - _setUpEventEmitters(view:viewModule.AppView, elementInjector:eli.ElementInjector, boundElementIndex:number) { - var emitters = elementInjector.getEventEmitterAccessors(); - for(var directiveIndex = 0; directiveIndex < emitters.length; ++directiveIndex) { - var directiveEmitters = emitters[directiveIndex]; - var directive = elementInjector.getDirectiveAtIndex(directiveIndex); - - for (var eventIndex = 0; eventIndex < directiveEmitters.length; ++eventIndex) { - var eventEmitterAccessor = directiveEmitters[eventIndex]; - eventEmitterAccessor.subscribe(view, boundElementIndex, directive); - } - } - } - - /** - * This should only be called by View or ViewContainer. - */ - _viewDehydrateRecurse(view:viewModule.AppView) { - // Note: preserve the opposite order of the hydration process. - - // componentChildViews - for (var i = 0; i < view.componentChildViews.length; i++) { - var componentView = view.componentChildViews[i]; - if (isPresent(componentView)) { - this._viewDehydrateRecurse(componentView); - var binder = view.proto.elementBinders[i]; - if (binder.hasDynamicComponent()) { - view.changeDetector.removeShadowDomChild(componentView.changeDetector); - view.componentChildViews[i] = null; - this._viewFactory.returnView(componentView); - } - } - } - - // imperativeHostViews - for (var i = 0; i < view.imperativeHostViews.length; i++) { - var hostView = view.imperativeHostViews[i]; - this._viewDehydrateRecurse(hostView); - view.changeDetector.removeChild(hostView.changeDetector); - this._viewFactory.returnView(hostView); - } - view.imperativeHostViews = []; - - // elementInjectors - for (var i = 0; i < view.elementInjectors.length; i++) { - if (isPresent(view.elementInjectors[i])) { - view.elementInjectors[i].clearDirectives(); - } - } - - // viewContainers - if (isPresent(view.viewContainers)) { - for (var i = 0; i < view.viewContainers.length; i++) { - var vc = view.viewContainers[i]; - if (isPresent(vc)) { - this._viewContainerDehydrateRecurse(vc); - } - } - } - - view.render = null; - - if (isPresent(view.locals)) { - view.locals.clearValues(); - } - view.context = null; - view.changeDetector.dehydrate(); - } - - _createShadowDomAppInjector(componentDirective, appInjector) { - var shadowDomAppInjector = null; - - // shadowDomAppInjector - var injectables = componentDirective.resolvedInjectables; - if (isPresent(injectables)) { - shadowDomAppInjector = appInjector.createChildFromResolved(injectables); - } else { - shadowDomAppInjector = appInjector; - } - return shadowDomAppInjector; - } - - /** - * This should only be called by View or ViewContainer. - */ - _viewContainerDehydrateRecurse(viewContainer:vcModule.ViewContainer) { - for (var i=0; i, + renderComponentIndex:number):number { + this._renderer.setEventDispatcher(view.render, view); + + var binders = view.proto.elementBinders; + for (var i = 0; i < binders.length; ++i) { + if (binders[i].hasStaticComponent()) { + var childView = view.componentChildViews[i]; + childView.render = renderComponentViewRefs[renderComponentIndex++]; + this._utils.hydrateComponentView(view, i); + renderComponentIndex = this._viewHydrateRecurse( + view.componentChildViews[i], + renderComponentViewRefs, + renderComponentIndex + ); + } + } + return renderComponentIndex; + } + + _viewDehydrateRecurse(view:viewModule.AppView) { + var binders = view.proto.elementBinders; + for (var i = 0; i < binders.length; i++) { + var componentView = view.componentChildViews[i]; + if (isPresent(componentView)) { + this._utils.dehydrateView(componentView); + this._viewDehydrateRecurse(componentView); + if (binders[i].hasDynamicComponent()) { + this._utils.detachComponentView(view, i); + this._destroyView(componentView); + } + } + var vc = view.viewContainers[i]; + if (isPresent(vc)) { + for (var j = vc.views.length - 1; j >= 0; j--) { + var childView = vc.views[j]; + this._utils.dehydrateView(childView); + this._utils.detachViewInContainer(view, i, j); + this._destroyView(childView); + } + } + } + + // imperativeHostViews + for (var i = 0; i < view.imperativeHostViews.length; i++) { + var hostView = view.imperativeHostViews[i]; + this._viewDehydrateRecurse(hostView); + this._utils.dehydrateAndDetachInPlaceHostView(view, hostView); + this._destroyView(hostView); + } + view.render = null; + } +} \ No newline at end of file diff --git a/modules/angular2/src/core/compiler/view_manager_utils.js b/modules/angular2/src/core/compiler/view_manager_utils.js new file mode 100644 index 0000000000..2b5bae64a4 --- /dev/null +++ b/modules/angular2/src/core/compiler/view_manager_utils.js @@ -0,0 +1,243 @@ +import {Injectable, Injector, Binding} from 'angular2/di'; +import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; +import * as eli from './element_injector'; +import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; +import {NgElement} from 'angular2/src/core/compiler/ng_element'; +import * as viewModule from './view'; +import * as avmModule from './view_manager'; +import {Renderer} from 'angular2/src/render/api'; +import {BindingPropagationConfig, Locals} from 'angular2/change_detection'; +import {DirectiveMetadataReader} from './directive_metadata_reader'; + +@Injectable() +export class AppViewManagerUtils { + _metadataReader:DirectiveMetadataReader; + + constructor(metadataReader:DirectiveMetadataReader) { + this._metadataReader = metadataReader; + } + + createView(protoView:viewModule.AppProtoView, viewManager:avmModule.AppViewManager, renderer:Renderer): viewModule.AppView { + var view = new viewModule.AppView(renderer, protoView, protoView.protoLocals); + var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindings, + protoView.getVariableBindings(), protoView.getdirectiveRecords()); + + var binders = protoView.elementBinders; + var elementInjectors = ListWrapper.createFixedSize(binders.length); + var rootElementInjectors = []; + var preBuiltObjects = ListWrapper.createFixedSize(binders.length); + var componentChildViews = ListWrapper.createFixedSize(binders.length); + + for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { + var binder = binders[binderIdx]; + var elementInjector = null; + + // elementInjectors and rootElementInjectors + var protoElementInjector = binder.protoElementInjector; + if (isPresent(protoElementInjector)) { + if (isPresent(protoElementInjector.parent)) { + var parentElementInjector = elementInjectors[protoElementInjector.parent.index]; + elementInjector = protoElementInjector.instantiate(parentElementInjector); + } else { + elementInjector = protoElementInjector.instantiate(null); + ListWrapper.push(rootElementInjectors, elementInjector); + } + } + elementInjectors[binderIdx] = elementInjector; + + // preBuiltObjects + if (isPresent(elementInjector)) { + var defaultProtoView = isPresent(binder.viewportDirective) ? binder.nestedProtoView : null; + preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(viewManager, view, new NgElement(view, binderIdx), defaultProtoView); + } + } + + view.init(changeDetector, elementInjectors, rootElementInjectors, + preBuiltObjects, componentChildViews); + + return view; + } + + attachComponentView(hostView:viewModule.AppView, boundElementIndex:number, + componentView:viewModule.AppView) { + var childChangeDetector = componentView.changeDetector; + hostView.changeDetector.addShadowDomChild(childChangeDetector); + hostView.componentChildViews[boundElementIndex] = componentView; + } + + detachComponentView(hostView:viewModule.AppView, boundElementIndex:number) { + var componentView = hostView.componentChildViews[boundElementIndex]; + hostView.changeDetector.removeShadowDomChild(componentView.changeDetector); + hostView.componentChildViews[boundElementIndex] = null; + } + + hydrateComponentView(hostView:viewModule.AppView, boundElementIndex:number, injector:Injector = null) { + var elementInjector = hostView.elementInjectors[boundElementIndex]; + var componentView = hostView.componentChildViews[boundElementIndex]; + var binder = hostView.proto.elementBinders[boundElementIndex]; + var component; + if (binder.hasDynamicComponent()) { + component = elementInjector.getDynamicallyLoadedComponent(); + } else { + component = elementInjector.getComponent(); + } + this._hydrateView( + componentView, injector, elementInjector, component, null + ); + } + + attachAndHydrateInPlaceHostView(parentComponentHostView:viewModule.AppView, parentComponentBoundElementIndex:number, + hostView:viewModule.AppView, injector:Injector = null) { + var hostElementInjector = null; + if (isPresent(parentComponentHostView)) { + hostElementInjector = parentComponentHostView.elementInjectors[parentComponentBoundElementIndex]; + var parentView = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex]; + parentView.changeDetector.addChild(hostView.changeDetector); + ListWrapper.push(parentView.imperativeHostViews, hostView); + } + this._hydrateView(hostView, injector, hostElementInjector, new Object(), null); + } + + dehydrateAndDetachInPlaceHostView(parentView:viewModule.AppView, + hostView:viewModule.AppView) { + this.dehydrateView(hostView); + + if (isPresent(parentView)) { + parentView.changeDetector.removeChild(hostView.changeDetector); + ListWrapper.remove(parentView.imperativeHostViews, hostView); + } + } + + attachViewInContainer(parentView:viewModule.AppView, boundElementIndex:number, atIndex:number, view:viewModule.AppView) { + parentView.changeDetector.addChild(view.changeDetector); + var viewContainer = parentView.viewContainers[boundElementIndex]; + if (isBlank(viewContainer)) { + viewContainer = new viewModule.InternalAppViewContainer(); + parentView.viewContainers[boundElementIndex] = viewContainer; + } + ListWrapper.insert(viewContainer.views, atIndex, view); + var sibling; + if (atIndex == 0) { + sibling = null; + } else { + sibling = ListWrapper.last(viewContainer.views[atIndex - 1].rootElementInjectors) + } + var elementInjector = parentView.elementInjectors[boundElementIndex]; + for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) { + view.rootElementInjectors[i].linkAfter(elementInjector, sibling); + } + } + + detachViewInContainer(parentView:viewModule.AppView, boundElementIndex:number, atIndex:number) { + var viewContainer = parentView.viewContainers[boundElementIndex]; + var view = viewContainer.views[atIndex]; + view.changeDetector.remove(); + ListWrapper.removeAt(viewContainer.views, atIndex); + for (var i = 0; i < view.rootElementInjectors.length; ++i) { + view.rootElementInjectors[i].unlink(); + } + } + + hydrateViewInContainer(parentView:viewModule.AppView, boundElementIndex:number, atIndex:number, injector:Injector) { + var viewContainer = parentView.viewContainers[boundElementIndex]; + var view = viewContainer.views[atIndex]; + var elementInjector = parentView.elementInjectors[boundElementIndex]; + this._hydrateView(view, injector, elementInjector, parentView.context, parentView.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}`); + } + if (isBlank(injector)) { + injector = elementInjector.getLightDomAppInjector(); + } + var annotation = this._metadataReader.read(componentBinding.token).annotation; + var componentDirective = eli.DirectiveBinding.createFromBinding(componentBinding, annotation); + var shadowDomAppInjector = this._createShadowDomAppInjector(componentDirective, injector); + elementInjector.dynamicallyCreateComponent(componentDirective, shadowDomAppInjector); + } + + _createShadowDomAppInjector(componentDirective, appInjector) { + var shadowDomAppInjector = null; + + // shadowDomAppInjector + var injectables = componentDirective.resolvedInjectables; + if (isPresent(injectables)) { + shadowDomAppInjector = appInjector.createChildFromResolved(injectables); + } else { + shadowDomAppInjector = appInjector; + } + return shadowDomAppInjector; + } + + _hydrateView(view:viewModule.AppView, appInjector:Injector, hostElementInjector:eli.ElementInjector, context: Object, parentLocals:Locals) { + if (isBlank(appInjector)) { + appInjector = hostElementInjector.getShadowDomAppInjector(); + } + if (isBlank(appInjector)) { + appInjector = hostElementInjector.getLightDomAppInjector(); + } + view.context = context; + view.locals.parent = parentLocals; + view.changeDetector.hydrate(view.context, view.locals, view); + + var binders = view.proto.elementBinders; + for (var i = 0; i < binders.length; ++i) { + var elementInjector = view.elementInjectors[i]; + if (isPresent(elementInjector)) { + var componentDirective = view.proto.elementBinders[i].componentDirective; + var shadowDomAppInjector = null; + if (isPresent(componentDirective)) { + shadowDomAppInjector = this._createShadowDomAppInjector(componentDirective, appInjector); + } else { + shadowDomAppInjector = null; + } + elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, view.preBuiltObjects[i]); + this._setUpEventEmitters(view, elementInjector, i); + + // The exporting of $implicit is a special case. Since multiple elements will all export + // the different values as $implicit, directly assign $implicit bindings to the variable + // name. + var exportImplicitName = elementInjector.getExportImplicitName(); + if (elementInjector.isExportingComponent()) { + view.locals.set(exportImplicitName, elementInjector.getComponent()); + } else if (elementInjector.isExportingElement()) { + view.locals.set(exportImplicitName, elementInjector.getNgElement().domElement); + } + } + } + + } + + _setUpEventEmitters(view:viewModule.AppView, elementInjector:eli.ElementInjector, boundElementIndex:number) { + var emitters = elementInjector.getEventEmitterAccessors(); + for (var directiveIndex = 0; directiveIndex < emitters.length; ++directiveIndex) { + var directiveEmitters = emitters[directiveIndex]; + var directive = elementInjector.getDirectiveAtIndex(directiveIndex); + + for (var eventIndex = 0; eventIndex < directiveEmitters.length; ++eventIndex) { + var eventEmitterAccessor = directiveEmitters[eventIndex]; + eventEmitterAccessor.subscribe(view, boundElementIndex, directive); + } + } + } + + dehydrateView(view:viewModule.AppView) { + var binders = view.proto.elementBinders; + for (var i = 0; i < binders.length; ++i) { + var elementInjector = view.elementInjectors[i]; + if (isPresent(elementInjector)) { + elementInjector.clearDirectives(); + } + } + if (isPresent(view.locals)) { + view.locals.clearValues(); + } + view.context = null; + view.changeDetector.dehydrate(); + } + +} \ No newline at end of file diff --git a/modules/angular2/src/core/compiler/view_pool.js b/modules/angular2/src/core/compiler/view_pool.js new file mode 100644 index 0000000000..df21776cd4 --- /dev/null +++ b/modules/angular2/src/core/compiler/view_pool.js @@ -0,0 +1,40 @@ +import {Inject, OpaqueToken} from 'angular2/di'; +import {ListWrapper, MapWrapper, Map, List} from 'angular2/src/facade/collection'; +import {isPresent, isBlank} from 'angular2/src/facade/lang'; + +import * as viewModule from './view'; + + +// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this! +export const APP_VIEW_POOL_CAPACITY = 'AppViewPool.viewPoolCapacity'; + +export class AppViewPool { + _poolCapacityPerProtoView:number; + _pooledViewsPerProtoView:Map>; + + constructor(@Inject(APP_VIEW_POOL_CAPACITY) poolCapacityPerProtoView) { + this._poolCapacityPerProtoView = poolCapacityPerProtoView; + this._pooledViewsPerProtoView = MapWrapper.create(); + } + + getView(protoView:viewModule.AppProtoView):viewModule.AppView { + var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView); + if (isPresent(pooledViews) && pooledViews.length > 0) { + return ListWrapper.removeLast(pooledViews); + } + return null; + } + + returnView(view:viewModule.AppView) { + var protoView = view.proto; + var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView); + if (isBlank(pooledViews)) { + pooledViews = []; + MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews); + } + if (pooledViews.length < this._poolCapacityPerProtoView) { + ListWrapper.push(pooledViews, view); + } + } + +} \ No newline at end of file diff --git a/modules/angular2/src/test_lib/test_bed.js b/modules/angular2/src/test_lib/test_bed.js index c1b90ce39b..398e3ddcf3 100644 --- a/modules/angular2/src/test_lib/test_bed.js +++ b/modules/angular2/src/test_lib/test_bed.js @@ -8,13 +8,8 @@ import {List} from 'angular2/src/facade/collection'; import {View} from 'angular2/src/core/annotations/view'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; -import {Compiler} from 'angular2/src/core/compiler/compiler'; import {AppView} from 'angular2/src/core/compiler/view'; -import {ViewFactory} from 'angular2/src/core/compiler/view_factory'; -import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator'; - -import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; -import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; +import {DynamicComponentLoader, ComponentRef} from 'angular2/src/core/compiler/dynamic_component_loader'; import {queryView, viewRootNodes, el} from './utils'; import {instantiateType, getTypeOf} from './lang_utils'; @@ -92,18 +87,9 @@ export class TestBed { } var rootEl = el('
'); - var metadataReader = this._injector.get(DirectiveMetadataReader); - var componentBinding = DirectiveBinding.createFromBinding( - bind(component).toValue(context), - metadataReader.read(component).annotation - ); - return this._injector.get(Compiler).compileInHost(componentBinding).then((pv) => { - var viewFactory = this._injector.get(ViewFactory); - var viewHydrator = this._injector.get(AppViewHydrator); - var hostView = viewFactory.getView(pv); - viewHydrator.hydrateInPlaceHostView(null, rootEl, hostView, this._injector); - - return new ViewProxy(this._injector, hostView.componentChildViews[0]); + var componentBinding = bind(component).toValue(context); + return this._injector.get(DynamicComponentLoader).loadIntoNewLocation(componentBinding, null, rootEl, this._injector).then((hostComponentRef) => { + return new ViewProxy(hostComponentRef); }); } } @@ -112,12 +98,12 @@ export class TestBed { * Proxy to `AppView` return by `createView` in {@link TestBed} which offers a high level API for tests. */ export class ViewProxy { + _componentRef: ComponentRef; _view: AppView; - _injector: Injector; - constructor(injector: Injector, view: AppView) { - this._view = view; - this._injector = injector; + constructor(componentRef: ComponentRef) { + this._componentRef = componentRef; + this._view = componentRef.hostView.componentChildViews[0]; } get context(): any { @@ -137,8 +123,7 @@ export class ViewProxy { } destroy() { - var viewHydrator = this._injector.get(AppViewHydrator); - viewHydrator.dehydrateInPlaceHostView(null, this._view); + this._componentRef.dispose(); } /** diff --git a/modules/angular2/src/test_lib/test_injector.js b/modules/angular2/src/test_lib/test_injector.js index 9584366dfc..70148bc79e 100644 --- a/modules/angular2/src/test_lib/test_injector.js +++ b/modules/angular2/src/test_lib/test_injector.js @@ -35,8 +35,9 @@ import {Injector} from 'angular2/di'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import {FunctionWrapper} from 'angular2/src/facade/lang'; -import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory'; -import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator'; +import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; +import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; +import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {Renderer} from 'angular2/src/render/api'; import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; @@ -86,9 +87,10 @@ function _getAppBindings() { rvh.RenderViewHydrator, bind(rvf.VIEW_POOL_CAPACITY).toValue(500), ProtoViewFactory, - ViewFactory, - AppViewHydrator, - bind(VIEW_POOL_CAPACITY).toValue(500), + AppViewPool, + AppViewManager, + AppViewManagerUtils, + bind(APP_VIEW_POOL_CAPACITY).toValue(500), Compiler, CompilerCache, bind(TemplateResolver).toClass(MockTemplateResolver), diff --git a/modules/angular2/src/test_lib/test_lib.dart b/modules/angular2/src/test_lib/test_lib.dart index f30cae7358..c65fe33696 100644 --- a/modules/angular2/src/test_lib/test_lib.dart +++ b/modules/angular2/src/test_lib/test_lib.dart @@ -18,8 +18,6 @@ import 'package:angular2/src/facade/collection.dart' show StringMapWrapper; import './test_injector.dart'; export './test_injector.dart' show inject; -import 'package:collection/equality.dart'; - bool IS_DARTIUM = true; List _testBindings = []; diff --git a/modules/angular2/test/core/compiler/compiler_spec.js b/modules/angular2/test/core/compiler/compiler_spec.js index eda7686181..6ca3d46ae4 100644 --- a/modules/angular2/test/core/compiler/compiler_spec.js +++ b/modules/angular2/test/core/compiler/compiler_spec.js @@ -414,6 +414,13 @@ export function main() { async.done(); }); })); + + it('should throw for non component types', () => { + var compiler = createCompiler([], []); + expect( + () => compiler.compile(SomeDecoratorDirective) + ).toThrowError(`Could not load '${stringify(SomeDecoratorDirective)}' because it is not a component.`); + }); }); } diff --git a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js index ac93c2157b..90dbf7e0da 100644 --- a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js +++ b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js @@ -69,7 +69,7 @@ export function main() { tb.createView(MyComp).then((view) => { view.context.ctxBoolProp = true; view.detectChanges(); - var dynamicComponent = view.rawView.viewContainers[0].get(0).locals.get("dynamic"); + var dynamicComponent = view.rawView.viewContainers[0].views[0].locals.get("dynamic"); dynamicComponent.done.then((_) => { view.detectChanges(); expect(view.rootNodes).toHaveText('hello'); @@ -77,13 +77,13 @@ export function main() { view.context.ctxBoolProp = false; view.detectChanges(); - expect(view.rawView.viewContainers[0].length).toBe(0); + 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].get(0).locals.get("dynamic"); + var dynamicComponent = view.rawView.viewContainers[0].views[0].locals.get("dynamic"); return dynamicComponent.done; }).then((_) => { view.detectChanges(); diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index acc3f66444..16aabb8915 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -21,7 +21,16 @@ class DummyDirective extends Directive { @proxy @IMPLEMENTS(AppView) -class DummyView extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}} +class DummyView extends SpyObject { + componentChildViews; + changeDetector; + constructor() { + super(); + this.componentChildViews = []; + this.changeDetector = null; + } + noSuchMethod(m){return super.noSuchMethod(m);} +} class SimpleDirective { @@ -119,6 +128,20 @@ class NeedsElementRef { } } +class NeedsViewContainer { + viewContainer; + constructor(vc:ViewContainer) { + this.viewContainer = vc; + } +} + +class NeedsChangeDetectorRef { + changeDetectorRef; + constructor(cdr:ChangeDetectorRef) { + this.changeDetectorRef = cdr; + } +} + class A_Needs_B { constructor(dep){} } @@ -158,7 +181,7 @@ class TestNode extends TreeNode { } export function main() { - var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null); + var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null, null); var appInjector = Injector.resolveAndCreate([]); function humanize(tree, names:List) { @@ -431,7 +454,7 @@ export function main() { it("should instantiate directives that depend on pre built objects", function () { var view = new DummyView(); - var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null, null)); + var inj = injector([NeedsView], null, null, new PreBuiltObjects(null, view, null, null)); expect(inj.get(NeedsView).view).toBe(view); }); @@ -557,34 +580,23 @@ export function main() { describe("pre built objects", function () { it("should return view", function () { var view = new DummyView(); - var inj = injector([], null, null, new PreBuiltObjects(view, null, null)); + var inj = injector([], null, null, new PreBuiltObjects(null, view, null, null)); expect(inj.get(AppView)).toEqual(view); }); it("should return element", function () { var element = new NgElement(null, null); - var inj = injector([], null, null, new PreBuiltObjects(null, element, null)); + var inj = injector([], null, null, new PreBuiltObjects(null, null, element, null)); expect(inj.get(NgElement)).toEqual(element); }); - it('should return viewContainer', function () { - var viewContainer = new ViewContainer(null, null, null); - var view = new DummyView(); - view.spy('getOrCreateViewContainer').andCallFake( (index) => { - return viewContainer; - }); - var inj = injector([], null, null, new PreBuiltObjects(view, null, null)); + it("should return default ProtoView", function () { + var protoView = new AppProtoView(null, null); + var inj = injector([], null, null, new PreBuiltObjects(null, null, null, protoView)); - expect(inj.get(ViewContainer)).toEqual(viewContainer); - }); - - it('should return changeDetectorRef', function () { - var cd = new DynamicChangeDetector(null, null, null, [], []); - var inj = injector([], null, null, new PreBuiltObjects(null, null, cd)); - - expect(inj.get(ChangeDetectorRef)).toBe(cd.ref); + expect(inj.get(AppProtoView)).toEqual(protoView); }); }); @@ -694,14 +706,20 @@ export function main() { expect(inj.get(NeedsElementRef).elementRef).toBeAnInstanceOf(ElementRef); }); - it('should return the viewContainer from the view', () => { - var viewContainer = new ViewContainer(null, null, null); + it('should inject ChangeDetectorRef', function () { + var cd = new DynamicChangeDetector(null, null, null, [], []); var view = new DummyView(); - view.spy('getOrCreateViewContainer').andCallFake( (index) => { - return viewContainer; - }); - var inj = injector([NeedsElementRef], null, null, new PreBuiltObjects(view, null, null)); - expect(inj.get(NeedsElementRef).elementRef.viewContainer).toBe(viewContainer); + var childView = new DummyView(); + childView.changeDetector = cd; + view.componentChildViews = [childView]; + var inj = injector([NeedsChangeDetectorRef], null, null, new PreBuiltObjects(null, view, null, null)); + + expect(inj.get(NeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref); + }); + + it('should inject ViewContainer', () => { + var inj = injector([NeedsViewContainer]); + expect(inj.get(NeedsViewContainer).viewContainer).toBeAnInstanceOf(ViewContainer); }); }); diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index c8351f87f7..0d4b4bf618 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -537,7 +537,7 @@ export function main() { tb.createView(MyComp, {context: ctx}).then((view) => { view.detectChanges(); - var subview = view.rawView.viewContainers[1].get(0); + var subview = view.rawView.viewContainers[1].views[0]; var childComponent = subview.locals.get('child'); expect(childComponent.myAncestor).toBeAnInstanceOf(SomeDirective); @@ -665,7 +665,7 @@ export function main() { ctx.ctxBoolProp = true; view.detectChanges(); - var subview = view.rawView.viewContainers[0].get(0); + var subview = view.rawView.viewContainers[0].views[0]; var injector = subview.elementInjectors[0]; var listener = injector.get(DecoratorListeningDomEvent); var listenerother = injector.get(DecoratorListeningDomEventOther); diff --git a/modules/angular2/test/core/compiler/view_container_spec.js b/modules/angular2/test/core/compiler/view_container_spec.js new file mode 100644 index 0000000000..e34417d478 --- /dev/null +++ b/modules/angular2/test/core/compiler/view_container_spec.js @@ -0,0 +1,75 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + xdescribe, + describe, + el, + dispatchEvent, + expect, + iit, + inject, + beforeEachBindings, + it, + xit, + SpyObject, proxy +} from 'angular2/test_lib'; + +import {MapWrapper} from 'angular2/src/facade/collection'; +import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang'; + +import {ElementRef} from 'angular2/src/core/compiler/element_injector'; +import {AppView, AppProtoView, InternalAppViewContainer} from 'angular2/src/core/compiler/view'; +import {ViewContainer} from 'angular2/src/core/compiler/view_container'; +import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; + +export function main() { + // TODO(tbosch): add missing tests + + describe('ViewContainer', () => { + var location; + var view; + var viewManager; + + function createProtoView() { + return new AppProtoView(null, null); + } + + function createView() { + return new AppView(null, createProtoView(), MapWrapper.create()); + } + + function createViewContainer(defaultProtoView = null) { + return new ViewContainer(viewManager, location, defaultProtoView); + } + + beforeEach( () => { + viewManager = new AppViewManagerSpy(); + view = createView(); + view.viewContainers = [null]; + location = new ElementRef(null, view, 0, null, null, null); + }); + + it('should return a 0 length if there is no underlying ViewContainer', () => { + var vc = createViewContainer(); + expect(vc.length).toBe(0); + }); + + it('should return the size of the underlying ViewContainer', () => { + var vc = createViewContainer(); + view.viewContainers = [new InternalAppViewContainer()]; + view.viewContainers[0].views = [createView()]; + expect(vc.length).toBe(1); + }); + + // TODO: add missing tests here! + + }); +} + +@proxy +@IMPLEMENTS(AppViewManager) +class AppViewManagerSpy extends SpyObject { + constructor(){super(AppViewManager);} + noSuchMethod(m){return super.noSuchMethod(m)} +} diff --git a/modules/angular2/test/core/compiler/view_factory_spec.js b/modules/angular2/test/core/compiler/view_factory_spec.js deleted file mode 100644 index 43194c162b..0000000000 --- a/modules/angular2/test/core/compiler/view_factory_spec.js +++ /dev/null @@ -1,179 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - xdescribe, - describe, - el, - dispatchEvent, - expect, - iit, - inject, - beforeEachBindings, - it, - xit, - SpyObject, proxy -} from 'angular2/test_lib'; -import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang'; -import {ViewFactory} from 'angular2/src/core/compiler/view_factory'; -import {Renderer, ViewRef} from 'angular2/src/render/api'; -import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; -import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector'; -import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; -import {Component} from 'angular2/src/core/annotations/annotations'; -import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; -import {ChangeDetector, ProtoChangeDetector} from 'angular2/change_detection'; - -export function main() { - describe('AppViewFactory', () => { - var reader; - var renderer; - - beforeEach( () => { - renderer = new SpyRenderer(); - reader = new DirectiveMetadataReader(); - }); - - function createViewFactory({capacity}):ViewFactory { - return new ViewFactory(capacity, renderer); - } - - function createProtoChangeDetector() { - var pcd = new SpyProtoChangeDetector(); - pcd.spy('instantiate').andCallFake( (dispatcher, bindingRecords, variableBindings, directiveRecords) => { - return new SpyChangeDetector(); - }); - return pcd; - } - - function createProtoView(binders=null) { - if (isBlank(binders)) { - binders = []; - } - var pv = new AppProtoView(null, createProtoChangeDetector()); - pv.elementBinders = binders; - return pv; - } - - function createDirectiveBinding(type) { - var meta = reader.read(type); - return DirectiveBinding.createFromType(meta.type, meta.annotation); - } - - function createComponentElBinder(binding, nestedProtoView = null) { - var binder = new ElementBinder(0, null, 0, null, binding, null); - binder.nestedProtoView = nestedProtoView; - return binder; - } - - it('should create views without cache', () => { - var pv = createProtoView(); - var vf = createViewFactory({ - capacity: 0 - }); - expect(vf.getView(pv) instanceof AppView).toBe(true); - }); - - describe('caching', () => { - - it('should support multiple AppProtoViews', () => { - var pv1 = createProtoView(); - var pv2 = createProtoView(); - var vf = createViewFactory({ capacity: 2 }); - var view1 = vf.getView(pv1); - var view2 = vf.getView(pv2); - vf.returnView(view1); - vf.returnView(view2); - - expect(vf.getView(pv1)).toBe(view1); - expect(vf.getView(pv2)).toBe(view2); - }); - - it('should reuse the newest view that has been returned', () => { - var pv = createProtoView(); - var vf = createViewFactory({ capacity: 2 }); - var view1 = vf.getView(pv); - var view2 = vf.getView(pv); - vf.returnView(view1); - vf.returnView(view2); - - expect(vf.getView(pv)).toBe(view2); - }); - - it('should not add views when the capacity has been reached', () => { - var pv = createProtoView(); - var vf = createViewFactory({ capacity: 2 }); - var view1 = vf.getView(pv); - var view2 = vf.getView(pv); - var view3 = vf.getView(pv); - vf.returnView(view1); - vf.returnView(view2); - vf.returnView(view3); - - expect(vf.getView(pv)).toBe(view2); - expect(vf.getView(pv)).toBe(view1); - }); - - }); - - describe('child components', () => { - - var vf; - - beforeEach(() => { - vf = createViewFactory({capacity: 1}); - }); - - it('should create static child component views', () => { - var hostPv = createProtoView([ - createComponentElBinder( - createDirectiveBinding(SomeComponent), - createProtoView() - ) - ]); - var hostView = vf.getView(hostPv); - var shadowView = hostView.componentChildViews[0]; - expect(shadowView).toBeTruthy(); - expect(hostView.changeDetector.spy('addShadowDomChild')).toHaveBeenCalledWith(shadowView.changeDetector); - }); - - it('should not create dynamic child component views', () => { - var hostPv = createProtoView([ - createComponentElBinder( - createDirectiveBinding(SomeComponent), - null - ) - ]); - var hostView = vf.getView(hostPv); - var shadowView = hostView.componentChildViews[0]; - expect(shadowView).toBeFalsy(); - }); - - }); - - }); -} - -@Component({ selector: 'someComponent' }) -class SomeComponent {} - -@proxy -@IMPLEMENTS(Renderer) -class SpyRenderer extends SpyObject { - constructor(){super(Renderer);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ChangeDetector) -class SpyChangeDetector extends SpyObject { - constructor(){super(ChangeDetector);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ProtoChangeDetector) -class SpyProtoChangeDetector extends SpyObject { - constructor(){super(ProtoChangeDetector);} - noSuchMethod(m){return super.noSuchMethod(m)} -} diff --git a/modules/angular2/test/core/compiler/view_hydrator_spec.js b/modules/angular2/test/core/compiler/view_hydrator_spec.js deleted file mode 100644 index 65c0286dbd..0000000000 --- a/modules/angular2/test/core/compiler/view_hydrator_spec.js +++ /dev/null @@ -1,282 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - xdescribe, - describe, - el, - dispatchEvent, - expect, - iit, - inject, - beforeEachBindings, - it, - xit, - SpyObject, proxy -} from 'angular2/test_lib'; -import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang'; -import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; - -import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; -import {Renderer, ViewRef} from 'angular2/src/render/api'; -import {ChangeDetector} from 'angular2/change_detection'; -import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; -import {DirectiveBinding, ElementInjector, ElementRef} from 'angular2/src/core/compiler/element_injector'; -import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; -import {Component} from 'angular2/src/core/annotations/annotations'; -import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator'; -import {ViewFactory} from 'angular2/src/core/compiler/view_factory'; - -export function main() { - describe('AppViewHydrator', () => { - var renderer; - var reader; - var hydrator; - var viewFactory; - - beforeEach( () => { - renderer = new SpyRenderer(); - reader = new DirectiveMetadataReader(); - viewFactory = new SpyViewFactory(); - hydrator = new AppViewHydrator(renderer, viewFactory); - }); - - function createDirectiveBinding(type) { - var meta = reader.read(type); - return DirectiveBinding.createFromType(meta.type, meta.annotation); - } - - function createElementInjector(overrides) { - return SpyObject.stub(new SpyElementInjector(), { - 'isExportingComponent' : false, - 'isExportingElement' : false, - 'getEventEmitterAccessors' : [], - 'getComponent' : null - }, overrides); - } - - function createEmptyElBinder() { - return new ElementBinder(0, null, 0, null, null, null); - } - - function createComponentElBinder(binding, nestedProtoView = null) { - var binder = new ElementBinder(0, null, 0, null, binding, null); - binder.nestedProtoView = nestedProtoView; - return binder; - } - - function createProtoView(binders = null) { - if (isBlank(binders)) { - binders = []; - } - var res = new AppProtoView(null, null); - res.elementBinders = binders; - return res; - } - - function createHostProtoView(nestedProtoView) { - return createProtoView([ - createComponentElBinder( - createDirectiveBinding(SomeComponent), - nestedProtoView - ) - ]); - } - - function createEmptyView() { - var view = new AppView(renderer, null, createProtoView(), MapWrapper.create()); - var changeDetector = new SpyChangeDetector(); - view.init(changeDetector, [], [], [], []); - return view; - } - - function createHostView(pv, shadowView, componentInstance, elementInjectors = null) { - var view = new AppView(renderer, null, pv, MapWrapper.create()); - var changeDetector = new SpyChangeDetector(); - - var eis; - if (isPresent(elementInjectors)) { - eis = elementInjectors; - } else { - eis = [createElementInjector({'getComponent': componentInstance})]; - } - - view.init(changeDetector, eis, eis, ListWrapper.createFixedSize(eis.length), [shadowView]); - return view; - } - - function hydrate(view) { - hydrator.hydrateInPlaceHostView(null, null, view, null); - } - - function dehydrate(view) { - hydrator.dehydrateInPlaceHostView(null, view); - } - - describe('hydrateDynamicComponentView', () => { - - it('should not allow to use non component indices', () => { - var pv = createProtoView([createEmptyElBinder()]); - var view = createHostView(pv, null, null); - var shadowView = createEmptyView(); - expect( - () => hydrator.hydrateDynamicComponentView(new ElementRef(null, view, 0, null), shadowView, null, null) - ).toThrowError('There is no dynamic component directive at element 0'); - }); - - it('should not allow to use static component indices', () => { - var pv = createHostProtoView(createProtoView()); - var view = createHostView(pv, null, null); - var shadowView = createEmptyView(); - expect( - () => hydrator.hydrateDynamicComponentView(new ElementRef(null, view, 0, null), shadowView, null, null) - ).toThrowError('There is no dynamic component directive at element 0'); - }); - - it('should not allow to overwrite an existing component', () => { - var pv = createHostProtoView(null); - var shadowView = createEmptyView(); - var view = createHostView(pv, null, null); - renderer.spy('createDynamicComponentView').andReturn([new ViewRef(), new ViewRef()]); - var elRef = new ElementRef(null, view, 0, null); - hydrator.hydrateDynamicComponentView(elRef, shadowView, createDirectiveBinding(SomeComponent), null); - expect( - () => hydrator.hydrateDynamicComponentView(elRef, shadowView, null, null) - ).toThrowError('There already is a bound component at element 0'); - }); - - }); - - describe('hydrate... shared functionality', () => { - - it('should hydrate existing child components', () => { - var hostPv = createHostProtoView(createProtoView()); - var componentInstance = new Object(); - var shadowView = createEmptyView(); - var hostView = createHostView(hostPv, shadowView, componentInstance); - renderer.spy('createInPlaceHostView').andCallFake( (a,b,c) => { - return [new ViewRef(), new ViewRef()]; - }); - - hydrate(hostView); - - expect(shadowView.hydrated()).toBe(true); - }); - - it("should set up event listeners", () => { - var dir = new Object(); - - var hostPv = createProtoView([ - createComponentElBinder(createDirectiveBinding(SomeComponent)), - createEmptyElBinder() - ]); - - var spyEventAccessor1 = SpyObject.stub({"subscribe" : null}); - var ei1 = createElementInjector({ - 'getEventEmitterAccessors': [[spyEventAccessor1]], - 'getDirectiveAtIndex': dir - }); - - var spyEventAccessor2 = SpyObject.stub({"subscribe" : null}); - var ei2 = createElementInjector({ - 'getEventEmitterAccessors': [[spyEventAccessor2]], - 'getDirectiveAtIndex': dir - }); - - var shadowView = createEmptyView(); - var hostView = createHostView(hostPv, shadowView, null, [ei1, ei2]); - renderer.spy('createInPlaceHostView').andReturn([new ViewRef(), new ViewRef()]); - - hydrate(hostView); - - expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir); - expect(spyEventAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir); - }); - }); - - describe('dehydrate... shared functionality', () => { - var hostView; - var shadowView; - - function createAndHydrate(nestedProtoView) { - var componentInstance = new Object(); - shadowView = createEmptyView(); - var hostPv = createHostProtoView(nestedProtoView); - hostView = createHostView(hostPv, shadowView, componentInstance); - renderer.spy('createInPlaceHostView').andReturn([new ViewRef(), new ViewRef()]); - - hydrate(hostView); - } - - it('should dehydrate child components', () => { - createAndHydrate(createProtoView()); - dehydrate(hostView); - - expect(shadowView.hydrated()).toBe(false); - }); - - it('should not clear static child components', () => { - createAndHydrate(createProtoView()); - dehydrate(hostView); - - expect(hostView.componentChildViews[0]).toBe(shadowView); - expect(hostView.changeDetector.spy('removeShadowDomChild')).not.toHaveBeenCalled(); - expect(viewFactory.spy('returnView')).not.toHaveBeenCalled(); - }); - - it('should clear dynamic child components', () => { - createAndHydrate(null); - dehydrate(hostView); - - expect(hostView.componentChildViews[0]).toBe(null); - expect(hostView.changeDetector.spy('removeShadowDomChild')).toHaveBeenCalledWith(shadowView.changeDetector); - expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(shadowView); - }); - - it('should clear imperatively added child components', () => { - createAndHydrate(createProtoView()); - var impHostView = createHostView(createHostProtoView(createProtoView()), createEmptyView(), null); - shadowView.imperativeHostViews = [impHostView]; - - dehydrate(hostView); - - expect(shadowView.imperativeHostViews).toEqual([]); - expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(impHostView); - expect(shadowView.changeDetector.spy('removeChild')).toHaveBeenCalledWith(impHostView.changeDetector); - }); - - }); - - }); -} - -@Component({ selector: 'someComponent' }) -class SomeComponent {} - -@proxy -@IMPLEMENTS(Renderer) -class SpyRenderer extends SpyObject { - constructor(){super(Renderer);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ChangeDetector) -class SpyChangeDetector extends SpyObject { - constructor(){super(ChangeDetector);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ElementInjector) -class SpyElementInjector extends SpyObject { - constructor(){super(ElementInjector);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ViewFactory) -class SpyViewFactory extends SpyObject { - constructor(){super(ViewFactory);} - noSuchMethod(m){return super.noSuchMethod(m)} -} \ No newline at end of file diff --git a/modules/angular2/test/core/compiler/view_manager_spec.js b/modules/angular2/test/core/compiler/view_manager_spec.js new file mode 100644 index 0000000000..75b56835db --- /dev/null +++ b/modules/angular2/test/core/compiler/view_manager_spec.js @@ -0,0 +1,518 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + xdescribe, + describe, + el, + dispatchEvent, + expect, + iit, + inject, + beforeEachBindings, + it, + xit, + SpyObject, proxy +} from 'angular2/test_lib'; +import {Injector, bind} from 'angular2/di'; +import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang'; +import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; + +import {AppProtoView, AppView, InternalAppViewContainer} from 'angular2/src/core/compiler/view'; +import {Renderer, ViewRef, ProtoViewRef, ViewContainerRef} from 'angular2/src/render/api'; +import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; +import {DirectiveBinding, ElementInjector, ElementRef} from 'angular2/src/core/compiler/element_injector'; +import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; +import {Component} from 'angular2/src/core/annotations/annotations'; +import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; +import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; +import {AppViewPool} from 'angular2/src/core/compiler/view_pool'; + +export function main() { + // TODO(tbosch): add missing tests + + describe('AppViewManager', () => { + var renderer; + var utils; + var viewPool; + var manager; + var reader; + var createdViews; + var createdRenderViews; + + function elementRef(parentView, boundElementIndex) { + return new ElementRef(null, parentView, boundElementIndex, null, null, null); + } + + function createDirectiveBinding(type) { + var meta = reader.read(type); + return DirectiveBinding.createFromType(meta.type, meta.annotation); + } + + function createEmptyElBinder() { + return new ElementBinder(0, null, 0, null, null, null); + } + + function createComponentElBinder(nestedProtoView = null) { + var binding = createDirectiveBinding(SomeComponent); + var binder = new ElementBinder(0, null, 0, null, binding, null); + binder.nestedProtoView = nestedProtoView; + return binder; + } + + function createProtoView(binders = null) { + if (isBlank(binders)) { + binders = []; + } + var staticChildComponentCount = 0; + for (var i = 0; i < binders.length; i++) { + if (binders[i].hasStaticComponent()) { + staticChildComponentCount++; + } + } + var res = new AppProtoView(new MockProtoViewRef(staticChildComponentCount), null); + res.elementBinders = binders; + return res; + } + + function createElementInjector() { + return SpyObject.stub(new SpyElementInjector(), { + 'isExportingComponent' : false, + 'isExportingElement' : false, + 'getEventEmitterAccessors' : [], + 'getComponent' : null + }, {}); + } + + function createView(pv=null) { + if (isBlank(pv)) { + pv = createProtoView(); + } + var view = new AppView(renderer, pv, MapWrapper.create()); + var elementInjectors = ListWrapper.createFixedSize(pv.elementBinders.length); + for (var i=0; i { + reader = new DirectiveMetadataReader(); + renderer = new SpyRenderer(); + utils = new SpyAppViewManagerUtils(); + viewPool = new SpyAppViewPool(); + manager = new AppViewManager(viewPool, utils, renderer); + createdViews = []; + createdRenderViews = []; + + utils.spy('createView').andCallFake( (proto, _a, _b) => { + var view = createView(proto); + ListWrapper.push(createdViews, view); + return view; + }); + utils.spy('attachComponentView').andCallFake( (hostView, elementIndex, childView) => { + hostView.componentChildViews[elementIndex] = childView; + }); + utils.spy('attachViewInContainer').andCallFake( (parentView, elementIndex, atIndex, childView) => { + var viewContainer = parentView.viewContainers[elementIndex]; + if (isBlank(viewContainer)) { + viewContainer = new InternalAppViewContainer(); + parentView.viewContainers[elementIndex] = viewContainer; + } + ListWrapper.insert(viewContainer.views, atIndex, childView); + }); + var createRenderViewRefs = function(renderPvRef) { + var res = []; + for (var i=0; i { + return createRenderViewRefs(childPvRef); + }); + renderer.spy('createInPlaceHostView').andCallFake( (_a, _b, childPvRef) => { + return createRenderViewRefs(childPvRef); + }); + renderer.spy('createViewInContainer').andCallFake( (_a, _b, childPvRef) => { + return createRenderViewRefs(childPvRef); + }); + }); + + describe('createDynamicComponentView', () => { + + describe('basic functionality', () => { + var hostView, componentProtoView; + beforeEach( () => { + hostView = createView(createProtoView( + [createComponentElBinder(null)] + )); + hostView.render = new ViewRef(); + componentProtoView = createProtoView(); + }); + + it('should create the view', () => { + expect( + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null) + ).toBe(createdViews[0]); + expect(createdViews[0].proto).toBe(componentProtoView); + }); + + it('should get the view from the pool', () => { + var createdView; + viewPool.spy('getView').andCallFake( (protoView) => { + createdView = createView(protoView); + return createdView; + }); + expect( + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null) + ).toBe(createdView); + expect(utils.spy('createView')).not.toHaveBeenCalled(); + }); + + it('should attach the view', () => { + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null) + expect(utils.spy('attachComponentView')).toHaveBeenCalledWith(hostView, 0, createdViews[0]); + }); + + it('should hydrate the dynamic component', () => { + var injector = new Injector([], null, false); + var componentBinding = bind(SomeComponent).toClass(SomeComponent); + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, componentBinding, injector); + expect(utils.spy('hydrateDynamicComponentInElementInjector')).toHaveBeenCalledWith(hostView, 0, componentBinding, injector); + }); + + it('should hydrate the view', () => { + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null); + expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(hostView, 0); + }); + + it('should create and set the render view', () => { + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null); + expect(renderer.spy('createDynamicComponentView')).toHaveBeenCalledWith(hostView.render, 0, componentProtoView.render); + expect(createdViews[0].render).toBe(createdRenderViews[0]); + }); + + it('should set the event dispatcher', () => { + manager.createDynamicComponentView(elementRef(hostView, 0), 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(hostView, 0), 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(hostView, 0), componentProtoView, null, null) + ).toThrowError('There is no dynamic component directive at element 0'); + }); + + }); + + describe('recurse into static child component views', () => { + var hostView, componentProtoView, nestedProtoView; + beforeEach( () => { + hostView = createView(createProtoView( + [createComponentElBinder(null)] + )); + hostView.render = new ViewRef(); + nestedProtoView = createProtoView(); + componentProtoView = createProtoView([ + createComponentElBinder(nestedProtoView) + ]); + }); + + it('should create the view', () => { + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null); + expect(createdViews[0].proto).toBe(componentProtoView); + expect(createdViews[1].proto).toBe(nestedProtoView); + }); + + it('should hydrate the view', () => { + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null); + expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(createdViews[0], 0); + }); + + it('should set the render view', () => { + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null); + expect(createdViews[1].render).toBe(createdRenderViews[1]) + }); + + it('should set the event dispatcher', () => { + manager.createDynamicComponentView(elementRef(hostView, 0), componentProtoView, null, null); + var cmpView = createdViews[1]; + expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView); + }); + }); + }); + + describe('createDynamicComponentView', () => { + // TODO: implement this! + describe('recurse into static child component views', () => { + // TODO + }); + + describe('recurse into dynamic child component views', () => { + // TODO + }); + }); + + + describe('createInPlaceHostView', () => { + + // 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); + parentView.render = new ViewRef(); + hostProtoView = createProtoView( + [createComponentElBinder(null)] + ); + }); + + it('should create the view', () => { + expect( + manager.createInPlaceHostView(elementRef(parentHostView, 0), null, hostProtoView, null) + ).toBe(createdViews[0]); + expect(createdViews[0].proto).toBe(hostProtoView); + }); + + it('should attachAndHydrate the view', () => { + var injector = new Injector([], null, false); + manager.createInPlaceHostView(elementRef(parentHostView, 0), null, hostProtoView, injector); + expect(utils.spy('attachAndHydrateInPlaceHostView')).toHaveBeenCalledWith(parentHostView, 0, createdViews[0], injector); + }); + + it('should create and set the render view', () => { + var elementOrSelector = 'someSelector'; + manager.createInPlaceHostView(elementRef(parentHostView, 0), elementOrSelector, hostProtoView, null) + expect(renderer.spy('createInPlaceHostView')).toHaveBeenCalledWith(parentView.render, elementOrSelector, hostProtoView.render); + expect(createdViews[0].render).toBe(createdRenderViews[0]); + }); + + it('should set the event dispatcher', () => { + manager.createInPlaceHostView(elementRef(parentHostView, 0), null, hostProtoView, null); + var cmpView = createdViews[0]; + expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView); + }); + }); + + }); + + + describe('destroyInPlaceHostView', () => { + describe('basic functionality', () => { + var parentHostView, parentView, hostProtoView, hostView, hostRenderViewRef; + beforeEach( () => { + parentHostView = createView(createProtoView( + [createComponentElBinder(null)] + )); + parentView = createView(); + utils.attachComponentView(parentHostView, 0, parentView); + parentView.render = new ViewRef(); + hostProtoView = createProtoView( + [createComponentElBinder(null)] + ); + hostView = manager.createInPlaceHostView(elementRef(parentHostView, 0), null, hostProtoView, null); + hostRenderViewRef = hostView.render; + }); + + it('should dehydrateAndDetach', () => { + manager.destroyInPlaceHostView(elementRef(parentHostView, 0), hostView); + expect(utils.spy('dehydrateAndDetachInPlaceHostView')).toHaveBeenCalledWith(parentView, hostView); + }); + + it('should destroy and clear the render view', () => { + manager.destroyInPlaceHostView(elementRef(parentHostView, 0), hostView); + expect(renderer.spy('destroyInPlaceHostView')).toHaveBeenCalledWith(parentView.render, hostRenderViewRef); + expect(hostView.render).toBe(null); + }); + + it('should return the view to the pool', () => { + manager.destroyInPlaceHostView(elementRef(parentHostView, 0), hostView); + expect(viewPool.spy('returnView')).toHaveBeenCalledWith(hostView); + }); + }); + + describe('recurse into imperativeHostViews', () => { + // TODO + }); + + }); + + describe('createViewInContainer', () => { + + // 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()] + )); + parentView.render = new ViewRef(); + childProtoView = createProtoView(); + }); + + it('should create a ViewContainer if not yet existing', () => { + manager.createViewInContainer(elementRef(parentView, 0), 0, childProtoView, null); + expect(parentView.viewContainers[0]).toBeTruthy(); + }); + + it('should create the view', () => { + expect( + manager.createViewInContainer(elementRef(parentView, 0), 0, childProtoView, null) + ).toBe(createdViews[0]); + expect(createdViews[0].proto).toBe(childProtoView); + }); + + it('should attach the view', () => { + manager.createViewInContainer(elementRef(parentView, 0), 0, childProtoView, null) + expect(utils.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, createdViews[0]); + }); + + it('should hydrate the view', () => { + var injector = new Injector([], null, false); + manager.createViewInContainer(elementRef(parentView, 0), 0, childProtoView, injector); + expect(utils.spy('hydrateViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, injector); + }); + + it('should create and set the render view', () => { + manager.createViewInContainer(elementRef(parentView, 0), 0, childProtoView, null); + expect(renderer.spy('createViewInContainer')).toHaveBeenCalledWith( + new ViewContainerRef(parentView.render, 0), 0, childProtoView.render); + expect(createdViews[0].render).toBe(createdRenderViews[0]); + }); + + it('should set the event dispatcher', () => { + manager.createViewInContainer(elementRef(parentView, 0), 0, childProtoView, null); + var childView = createdViews[0]; + expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(childView.render, childView); + }); + + }); + }); + + describe('destroyViewInContainer', () => { + // Note: We don't add tests for recursion here as we assume that + // this is using the same mechanism as the other methods... + + describe('basic functionality', () => { + var parentView, childProtoView, childView; + beforeEach( () => { + parentView = createView(createProtoView( + [createEmptyElBinder()] + )); + parentView.render = new ViewRef(); + childProtoView = createProtoView(); + childView = manager.createViewInContainer(elementRef(parentView, 0), 0, childProtoView, null); + }); + + it('should dehydrate', () => { + manager.destroyViewInContainer(elementRef(parentView, 0), 0); + expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(parentView.viewContainers[0].views[0]); + }); + + it('should detach', () => { + manager.destroyViewInContainer(elementRef(parentView, 0), 0); + expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0); + }); + + it('should destroy and clear the render view', () => { + manager.destroyViewInContainer(elementRef(parentView, 0), 0); + expect(renderer.spy('destroyViewInContainer')).toHaveBeenCalledWith(new ViewContainerRef(parentView.render, 0), 0); + expect(childView.render).toBe(null); + }); + + it('should return the view to the pool', () => { + manager.destroyViewInContainer(elementRef(parentView, 0), 0); + expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView); + }); + }); + + describe('recurse into ViewContainers', () => { + // TODO + }); + + }); + + describe('attachViewInContainer', () => { + + }); + + describe('detachViewInContainer', () => { + + }); + + }); +} + +class MockProtoViewRef extends ProtoViewRef { + nestedComponentCount:number; + constructor(nestedComponentCount:number) { + super(); + this.nestedComponentCount = nestedComponentCount; + } +} + +@Component({ selector: 'someComponent' }) +class SomeComponent {} + +@proxy +@IMPLEMENTS(Renderer) +class SpyRenderer extends SpyObject { + constructor(){super(Renderer);} + noSuchMethod(m){return super.noSuchMethod(m)} +} + +@proxy +@IMPLEMENTS(AppViewPool) +class SpyAppViewPool extends SpyObject { + constructor(){super(AppViewPool);} + noSuchMethod(m){return super.noSuchMethod(m)} +} + +@proxy +@IMPLEMENTS(AppViewManagerUtils) +class SpyAppViewManagerUtils extends SpyObject { + constructor(){super(AppViewManagerUtils);} + noSuchMethod(m){return super.noSuchMethod(m)} +} + +@proxy +@IMPLEMENTS(ElementInjector) +class SpyElementInjector extends SpyObject { + constructor(){super(ElementInjector);} + noSuchMethod(m){return super.noSuchMethod(m)} +} diff --git a/modules/angular2/test/core/compiler/view_manager_utils_spec.js b/modules/angular2/test/core/compiler/view_manager_utils_spec.js new file mode 100644 index 0000000000..79fa5f8226 --- /dev/null +++ b/modules/angular2/test/core/compiler/view_manager_utils_spec.js @@ -0,0 +1,168 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + xdescribe, + describe, + el, + dispatchEvent, + expect, + iit, + inject, + beforeEachBindings, + it, + xit, + SpyObject, proxy +} from 'angular2/test_lib'; + +import {Injector, bind} from 'angular2/di'; +import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang'; +import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; + +import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; +import {ChangeDetector} from 'angular2/change_detection'; +import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; +import {DirectiveBinding, ElementInjector, ElementRef} from 'angular2/src/core/compiler/element_injector'; +import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; +import {Component} from 'angular2/src/core/annotations/annotations'; +import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; + +export function main() { + // TODO(tbosch): add more tests here! + + describe('AppViewManagerUtils', () => { + + var metadataReader; + var utils; + + function createInjector() { + return new Injector([], null, false); + } + + function createDirectiveBinding(type) { + var meta = metadataReader.read(type); + return DirectiveBinding.createFromType(meta.type, meta.annotation); + } + + function createEmptyElBinder() { + return new ElementBinder(0, null, 0, null, null, null); + } + + function createComponentElBinder(nestedProtoView = null) { + var binding = createDirectiveBinding(SomeComponent); + var binder = new ElementBinder(0, null, 0, null, binding, null); + binder.nestedProtoView = nestedProtoView; + return binder; + } + + function createProtoView(binders = null) { + if (isBlank(binders)) { + binders = []; + } + var res = new AppProtoView(null, null); + res.elementBinders = binders; + return res; + } + + function createElementInjector() { + return SpyObject.stub(new SpyElementInjector(), { + 'isExportingComponent' : false, + 'isExportingElement' : false, + 'getEventEmitterAccessors' : [], + 'getComponent' : null, + 'getDynamicallyLoadedComponent': null + }, {}); + } + + function createView(pv=null) { + if (isBlank(pv)) { + pv = createProtoView(); + } + var view = new AppView(null, pv, MapWrapper.create()); + var elementInjectors = ListWrapper.createFixedSize(pv.elementBinders.length); + for (var i=0; i { + metadataReader = new DirectiveMetadataReader(); + utils = new AppViewManagerUtils(metadataReader); + }); + + 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'); + }); + + }); + + describe('shared hydrate functionality', () => { + + it("should set up event listeners", () => { + var dir = new Object(); + + var hostPv = createProtoView([ + createComponentElBinder(null), + createEmptyElBinder() + ]); + var hostView = createView(hostPv); + var spyEventAccessor1 = SpyObject.stub({"subscribe" : null}); + SpyObject.stub(hostView.elementInjectors[0], { + 'getEventEmitterAccessors': [[spyEventAccessor1]], + 'getDirectiveAtIndex': dir + }); + var spyEventAccessor2 = SpyObject.stub({"subscribe" : null}); + SpyObject.stub(hostView.elementInjectors[1], { + 'getEventEmitterAccessors': [[spyEventAccessor2]], + 'getDirectiveAtIndex': dir + }); + + var shadowView = createView(); + utils.attachComponentView(hostView, 0, shadowView); + + utils.attachAndHydrateInPlaceHostView(null, null, hostView, createInjector()); + + expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir); + expect(spyEventAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir); + }); + + }); + + }); + +} + +@Component({ selector: 'someComponent' }) +class SomeComponent {} + +@proxy +@IMPLEMENTS(ElementInjector) +class SpyElementInjector extends SpyObject { + constructor(){super(ElementInjector);} + noSuchMethod(m){return super.noSuchMethod(m)} +} + +@proxy +@IMPLEMENTS(ChangeDetector) +class SpyChangeDetector extends SpyObject { + constructor(){super(ChangeDetector);} + noSuchMethod(m){return super.noSuchMethod(m)} +} diff --git a/modules/angular2/test/core/compiler/view_pool_spec.js b/modules/angular2/test/core/compiler/view_pool_spec.js new file mode 100644 index 0000000000..55c848eb3c --- /dev/null +++ b/modules/angular2/test/core/compiler/view_pool_spec.js @@ -0,0 +1,75 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + xdescribe, + describe, + el, + dispatchEvent, + expect, + iit, + inject, + beforeEachBindings, + it, + xit, + SpyObject, proxy +} from 'angular2/test_lib'; +import {AppViewPool} from 'angular2/src/core/compiler/view_pool'; +import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; +import {MapWrapper, Map} from 'angular2/src/facade/collection'; + +export function main() { + describe('AppViewPool', () => { + + function createViewPool({capacity}):AppViewPool { + return new AppViewPool(capacity); + } + + function createProtoView() { + return new AppProtoView(null, null); + } + + function createView(pv) { + return new AppView(null, pv, MapWrapper.create()); + } + + it('should support multiple AppProtoViews', () => { + var vf = createViewPool({ capacity: 2 }); + var pv1 = createProtoView(); + var pv2 = createProtoView(); + var view1 = createView(pv1); + var view2 = createView(pv2); + vf.returnView(view1); + vf.returnView(view2); + + expect(vf.getView(pv1)).toBe(view1); + expect(vf.getView(pv2)).toBe(view2); + }); + + it('should reuse the newest view that has been returned', () => { + var pv = createProtoView(); + var vf = createViewPool({ capacity: 2 }); + var view1 = createView(pv); + var view2 = createView(pv); + vf.returnView(view1); + vf.returnView(view2); + + expect(vf.getView(pv)).toBe(view2); + }); + + it('should not add views when the capacity has been reached', () => { + var pv = createProtoView(); + var vf = createViewPool({ capacity: 2 }); + var view1 = createView(pv); + var view2 = createView(pv); + var view3 = createView(pv); + vf.returnView(view1); + vf.returnView(view2); + vf.returnView(view3); + + expect(vf.getView(pv)).toBe(view2); + expect(vf.getView(pv)).toBe(view1); + }); + + }); +} diff --git a/modules/angular2/test/core/compiler/view_spec.js b/modules/angular2/test/core/compiler/view_spec.js deleted file mode 100644 index 80d2d2d493..0000000000 --- a/modules/angular2/test/core/compiler/view_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - xdescribe, - describe, - el, - dispatchEvent, - expect, - iit, - inject, - beforeEachBindings, - it, - xit, - SpyObject, proxy -} from 'angular2/test_lib'; - -import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang'; -import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; - -import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; -import {ViewContainer} from 'angular2/src/core/compiler/view_container'; -import {Renderer} from 'angular2/src/render/api'; -import {ChangeDetector} from 'angular2/change_detection'; -import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; -import {ElementInjector} from 'angular2/src/core/compiler/element_injector'; - -export function main() { - describe('AppView', () => { - var renderer; - - beforeEach( () => { - renderer = new SpyRenderer(); - }); - - function createElementInjector() { - return new SpyElementInjector(); - } - - function createEmptyElBinder() { - return new ElementBinder(0, null, 0, null, null, null); - } - - function createEmbeddedProtoViewElBinder(nestedProtoView) { - var binder = new ElementBinder(0, null, 0, null, null, null); - binder.nestedProtoView = nestedProtoView; - return binder; - } - - function createProtoView(binders = null) { - if (isBlank(binders)) { - binders = []; - } - var res = new AppProtoView(null, null); - res.elementBinders = binders; - return res; - } - - function createViewWithOneBoundElement(pv) { - var view = new AppView(renderer, null, pv, MapWrapper.create()); - var changeDetector = new SpyChangeDetector(); - var eij = createElementInjector(); - view.init(changeDetector, [eij], [eij], - [null], [null]); - return view; - } - - describe('getOrCreateViewContainer()', () => { - - it('should create a new container', () => { - var pv = createProtoView([createEmptyElBinder()]); - var view = createViewWithOneBoundElement(pv); - expect(view.getOrCreateViewContainer(0) instanceof ViewContainer).toBe(true); - }); - - it('should return an existing container', () => { - var pv = createProtoView([createEmptyElBinder()]); - var view = createViewWithOneBoundElement(pv); - var vc = view.getOrCreateViewContainer(0); - expect(view.getOrCreateViewContainer(0)).toBe(vc); - }); - - it('should store an existing nestedProtoView in the container', () => { - var defaultProtoView = createProtoView(); - var pv = createProtoView([createEmbeddedProtoViewElBinder(defaultProtoView)]); - var view = createViewWithOneBoundElement(pv); - var vc = view.getOrCreateViewContainer(0); - expect(vc.defaultProtoView).toBe(defaultProtoView); - }); - - }); - - }); -} - -@proxy -@IMPLEMENTS(Renderer) -class SpyRenderer extends SpyObject { - constructor(){super(Renderer);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ChangeDetector) -class SpyChangeDetector extends SpyObject { - constructor(){super(ChangeDetector);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ElementInjector) -class SpyElementInjector extends SpyObject { - constructor(){super(ElementInjector);} - noSuchMethod(m){return super.noSuchMethod(m)} -} diff --git a/modules/benchmarks/src/naive_infinite_scroll/index.js b/modules/benchmarks/src/naive_infinite_scroll/index.js index cd6070c3ca..8dee1b8d12 100644 --- a/modules/benchmarks/src/naive_infinite_scroll/index.js +++ b/modules/benchmarks/src/naive_infinite_scroll/index.js @@ -6,7 +6,7 @@ import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabil import {App} from './app'; -import {VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory'; +import {APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; import {bind} from 'angular2/di'; export function main() { @@ -15,7 +15,7 @@ export function main() { } function createBindings():List { - return [bind(VIEW_POOL_CAPACITY).toValue(100000)]; + return [bind(APP_VIEW_POOL_CAPACITY).toValue(100000)]; } export function setupReflector() { diff --git a/modules/benchmarks/src/tree/tree_benchmark.js b/modules/benchmarks/src/tree/tree_benchmark.js index f8818bb2e9..44cd233f2a 100644 --- a/modules/benchmarks/src/tree/tree_benchmark.js +++ b/modules/benchmarks/src/tree/tree_benchmark.js @@ -9,7 +9,7 @@ import {window, document, gc} from 'angular2/src/facade/browser'; import {getIntParameter, getStringParameter, bindAction} from 'angular2/src/test_lib/benchmark_util'; import {If} from 'angular2/directives'; import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; -import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory'; +import {APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; import * as rvf from 'angular2/src/render/dom/view/view_factory'; import {bind} from 'angular2/di'; @@ -17,7 +17,7 @@ function createBindings():List { var viewCacheCapacity = getStringParameter('viewcache') == 'true' ? 10000 : 1; return [ bind(rvf.VIEW_POOL_CAPACITY).toValue(viewCacheCapacity), - bind(VIEW_POOL_CAPACITY).toValue(viewCacheCapacity) + bind(APP_VIEW_POOL_CAPACITY).toValue(viewCacheCapacity) ]; }