refactor(view): introduce AppViewManager to consolidate logic

AppViewManager is the single entry point to changing the view hierarchy.
It is split between the manager itself which does coordination and
helper methods, so both are easily testable in isolation.

Also, ViewContainer is now only a pure reference to a bound element
with the previous functionality but does not contain the list of views
any more.

Part of #1477
This commit is contained in:
Tobias Bosch 2015-04-24 17:53:06 -07:00
parent f78406392b
commit bfa381b35a
27 changed files with 1514 additions and 1216 deletions

View File

@ -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<Binding> {
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,

View File

@ -88,13 +88,17 @@ export class Compiler {
// Create a hostView as if the compiler encountered <hostcmp></hostcmp>.
// Used for bootstrapping.
compileInHost(componentTypeOrBinding:any):Promise<AppProtoView> {
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<AppProtoView> {
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.`);
}
}
}

View File

@ -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<ComponentRef> {
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<ComponentRef> {
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<ComponentRef> {
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<ComponentRef> {
this._assertTypeIsComponent(type);
return this._compiler.compileInHost(type).then(hostProtoView => {
loadNextToExistingLocation(typeOrBinding, location:ElementRef, injector:Injector = null):Promise<ComponentRef> {
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;
}
}

View File

@ -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;
}

View File

@ -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<AppView>;
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<AppView>;
viewContainers: List<ViewContainer>;
viewContainers: List<InternalAppViewContainer>;
preBuiltObjects: List<PreBuiltObjects>;
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

View File

@ -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<viewModule.AppView>;
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);
}
}

View File

@ -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<viewModule.AppProtoView, List<viewModule.AppView>>;
_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;
}
}

View File

@ -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<renderApi.ViewRef>,
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<viewContainer.length; i++) {
var view = viewContainer.get(i);
view.changeDetector.remove();
this._viewDehydrateRecurse(view);
}
// Note: We don't call clear here,
// as we don't want to change the render side
// as the render side does its own recursion.
viewContainer.internalClearWithoutRender();
}
}

View File

@ -0,0 +1,201 @@
import {Injector, Injectable, Binding} from 'angular2/di';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import * as eli from './element_injector';
import * as viewModule from './view';
import {Renderer, ViewRef, ViewContainerRef} from 'angular2/src/render/api';
import {AppViewManagerUtils} from './view_manager_utils';
import {AppViewPool} from './view_pool';
/**
* Entry point for creating, moving views in the view hierarchy and destroying views.
* This manager contains all recursion and delegates to helper methods
* in AppViewManagerUtils and the Renderer, so unit tests get simpler.
*/
@Injectable()
export class AppViewManager {
_viewPool:AppViewPool;
_utils:AppViewManagerUtils;
_renderer:Renderer;
constructor(viewPool:AppViewPool, utils:AppViewManagerUtils, renderer:Renderer) {
this._renderer = renderer;
this._viewPool = viewPool;
this._utils = utils;
}
createDynamicComponentView(hostLocation:eli.ElementRef,
componentProtoView:viewModule.AppProtoView, componentBinding:Binding, injector:Injector):viewModule.AppView {
var hostView = hostLocation.hostView;
var boundElementIndex = hostLocation.boundElementIndex;
var binder = hostView.proto.elementBinders[boundElementIndex];
if (!binder.hasDynamicComponent()) {
throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`)
}
var componentView = this._createViewRecurse(componentProtoView);
var renderViewRefs = this._renderer.createDynamicComponentView(hostView.render, boundElementIndex, componentProtoView.render);
componentView.render = renderViewRefs[0];
this._utils.attachComponentView(hostView, boundElementIndex, componentView);
this._utils.hydrateDynamicComponentInElementInjector(hostView, boundElementIndex, componentBinding, injector);
this._utils.hydrateComponentView(hostView, boundElementIndex);
this._viewHydrateRecurse(componentView, renderViewRefs, 1);
return componentView;
}
createInPlaceHostView(parentComponentLocation:eli.ElementRef,
hostElementSelector, hostProtoView:viewModule.AppProtoView, injector:Injector):viewModule.AppView {
var parentComponentHostView = null;
var parentComponentBoundElementIndex = null;
var parentRenderViewRef = null;
if (isPresent(parentComponentLocation)) {
parentComponentHostView = parentComponentLocation.hostView;
parentComponentBoundElementIndex = parentComponentLocation.boundElementIndex;
parentRenderViewRef = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex].render;
}
var hostView = this._createViewRecurse(hostProtoView);
var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostView.proto.render);
hostView.render = renderViewRefs[0];
this._utils.attachAndHydrateInPlaceHostView(parentComponentHostView, parentComponentBoundElementIndex, hostView, injector);
this._viewHydrateRecurse(hostView, renderViewRefs, 1);
return hostView;
}
destroyInPlaceHostView(parentComponentLocation:eli.ElementRef, hostView:viewModule.AppView) {
var parentView = null;
var parentRenderViewRef = null;
if (isPresent(parentComponentLocation)) {
parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex];
parentRenderViewRef = parentView.render;
}
var hostViewRenderRef = hostView.render;
this._utils.dehydrateAndDetachInPlaceHostView(parentView, hostView);
this._viewDehydrateRecurse(hostView);
this._renderer.destroyInPlaceHostView(parentRenderViewRef, hostViewRenderRef);
this._destroyView(hostView);
}
createViewInContainer(viewContainerLocation:eli.ElementRef,
atIndex:number, protoView:viewModule.AppProtoView, injector:Injector = null):viewModule.AppView {
var parentView = viewContainerLocation.hostView;
var boundElementIndex:number = viewContainerLocation.boundElementIndex;
var view = this._createViewRecurse(protoView);
var renderViewRefs = this._renderer.createViewInContainer(this._getRenderViewContainerRef(parentView, boundElementIndex), atIndex, view.proto.render);
view.render = renderViewRefs[0];
this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view);
this._utils.hydrateViewInContainer(parentView, boundElementIndex, atIndex, injector);
this._viewHydrateRecurse(view, renderViewRefs, 1);
return view;
}
destroyViewInContainer(viewContainerLocation:eli.ElementRef, atIndex:number) {
var parentView = viewContainerLocation.hostView;
var boundElementIndex:number = viewContainerLocation.boundElementIndex;
var viewContainer = parentView.viewContainers[boundElementIndex];
var view = viewContainer.views[atIndex];
this._utils.dehydrateView(view);
this._viewDehydrateRecurse(view);
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
this._renderer.destroyViewInContainer(this._getRenderViewContainerRef(parentView, boundElementIndex), atIndex);
this._destroyView(view);
}
attachViewInContainer(viewContainerLocation:eli.ElementRef, atIndex:number, view:viewModule.AppView):viewModule.AppView {
var parentView = viewContainerLocation.hostView;
var boundElementIndex:number = viewContainerLocation.boundElementIndex;
this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view);
this._renderer.insertViewIntoContainer(this._getRenderViewContainerRef(parentView, boundElementIndex), atIndex, view.render);
return view;
}
detachViewInContainer(viewContainerLocation:eli.ElementRef, atIndex:number):viewModule.AppView {
var parentView = viewContainerLocation.hostView;
var boundElementIndex:number = viewContainerLocation.boundElementIndex;
var viewContainer = parentView.viewContainers[boundElementIndex];
var view = viewContainer.views[atIndex];
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
this._renderer.detachViewFromContainer(this._getRenderViewContainerRef(parentView, boundElementIndex), atIndex);
return view;
}
_getRenderViewContainerRef(parentView:viewModule.AppView, boundElementIndex:number) {
return new ViewContainerRef(parentView.render, boundElementIndex);
}
_createViewRecurse(protoView:viewModule.AppProtoView) {
var view = this._viewPool.getView(protoView);
if (isBlank(view)) {
view = this._utils.createView(protoView, this, this._renderer);
var binders = protoView.elementBinders;
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
if (binder.hasStaticComponent()) {
var childView = this._createViewRecurse(binder.nestedProtoView);
this._utils.attachComponentView(view, binderIdx, childView);
}
}
}
return view;
}
_destroyView(view:viewModule.AppView) {
this._viewPool.returnView(view);
}
_viewHydrateRecurse(
view:viewModule.AppView,
renderComponentViewRefs:List<ViewRef>,
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;
}
}

View File

@ -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();
}
}

View File

@ -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<viewModule.AppProtoView, List<viewModule.AppView>>;
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);
}
}
}

View File

@ -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('<div></div>');
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();
}
/**

View File

@ -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),

View File

@ -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 = [];

View File

@ -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.`);
});
});
}

View File

@ -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();

View File

@ -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 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);
});
var inj = injector([NeedsElementRef], null, null, new PreBuiltObjects(view, null, null));
expect(inj.get(NeedsElementRef).elementRef.viewContainer).toBe(viewContainer);
it('should inject ViewContainer', () => {
var inj = injector([NeedsViewContainer]);
expect(inj.get(NeedsViewContainer).viewContainer).toBeAnInstanceOf(ViewContainer);
});
});

View File

@ -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);

View File

@ -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)}
}

View File

@ -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)}
}

View File

@ -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)}
}

View File

@ -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<pv.elementBinders.length; i++) {
elementInjectors[i] = createElementInjector();
}
view.init(null,
elementInjectors,
[],
ListWrapper.createFixedSize(pv.elementBinders.length),
ListWrapper.createFixedSize(pv.elementBinders.length)
);
return view;
}
beforeEach( () => {
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<renderPvRef.nestedComponentCount+1; i++) {
var renderViewRef = new ViewRef();
ListWrapper.push(res, renderViewRef);
ListWrapper.push(createdRenderViews, renderViewRef);
}
return res;
}
renderer.spy('createDynamicComponentView').andCallFake( (_a, _b, childPvRef) => {
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)}
}

View File

@ -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<pv.elementBinders.length; i++) {
elementInjectors[i] = createElementInjector();
}
view.init(new SpyChangeDetector(),
elementInjectors,
[],
ListWrapper.createFixedSize(pv.elementBinders.length),
ListWrapper.createFixedSize(pv.elementBinders.length)
);
return view;
}
beforeEach( () => {
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)}
}

View File

@ -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);
});
});
}

View File

@ -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)}
}

View File

@ -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() {

View File

@ -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)
];
}