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