refactor(views): clean up creating views in place and extract view_hydrator

Major changes:
- `compiler.compileRoot(el, type)`
  -> `compiler.compileInHost(type) + viewHydrator.hydrateHostViewInPlace(el, view)`
- move all `hydrate`/`dehydrate` methods out of `View` and `ViewContainer` into
  a standalone class `view_hydrator` as private methods and provide new public
  methods dedicated to the individual use cases.

Note: This PR does not change the current functionality, only moves it
into different places.

See design discussion in #1351, in preparation for imperative views.
This commit is contained in:
Tobias Bosch
2015-04-15 21:51:30 -07:00
parent 97fc248e00
commit 923d90bce8
35 changed files with 1013 additions and 763 deletions

View File

@ -29,11 +29,13 @@ 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 {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';
import * as rc from 'angular2/src/render/dom/compiler/compiler';
import * as rvf from 'angular2/src/render/dom/view/view_factory';
import * as rvh from 'angular2/src/render/dom/view/view_hydrator';
import {
appComponentRefToken,
@ -98,14 +100,16 @@ function _injectorBindings(appComponentType): List<Binding> {
[rvf.VIEW_POOL_CAPACITY, EventManager, ShadowDomStrategy]
),
bind(rvf.VIEW_POOL_CAPACITY).toValue(10000),
rvh.RenderViewHydrator,
ProtoViewFactory,
// TODO(tbosch): We need an explicit factory here, as
// we are getting errors in dart2js with mirrors...
bind(ViewFactory).toFactory(
(capacity) => new ViewFactory(capacity),
[VIEW_POOL_CAPACITY]
(capacity, renderer) => new ViewFactory(capacity, renderer),
[VIEW_POOL_CAPACITY, Renderer]
),
bind(VIEW_POOL_CAPACITY).toValue(10000),
AppViewHydrator,
Compiler,
CompilerCache,
TemplateResolver,

View File

@ -81,11 +81,11 @@ export class Compiler {
return DirectiveBinding.createFromType(meta.type, meta.annotation);
}
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>.
// Create a hostView as if the compiler encountered <hostcmp></hostcmp>.
// Used for bootstrapping.
compileRoot(elementOrSelector, componentTypeOrBinding:any):Promise<AppProtoView> {
return this._renderer.createRootProtoView(elementOrSelector, 'root').then( (rootRenderPv) => {
return this._compileNestedProtoViews(null, rootRenderPv, [this._bindDirective(componentTypeOrBinding)], true);
compileInHost(componentTypeOrBinding:any):Promise<AppProtoView> {
return this._renderer.createHostProtoView('host').then( (hostRenderPv) => {
return this._compileNestedProtoViews(null, hostRenderPv, [this._bindDirective(componentTypeOrBinding)], true);
});
}

View File

@ -2,12 +2,11 @@ import {Key, Injector, Injectable, ResolvedBinding} from 'angular2/di'
import {Compiler} from './compiler';
import {DirectiveMetadataReader} from './directive_metadata_reader';
import {Type, BaseException, stringify, isPresent} from 'angular2/src/facade/lang';
import {List} from 'angular2/src/facade/collection';
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 {Renderer} from 'angular2/src/render/api';
import {ElementRef} from './element_injector';
import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator';
import {ElementRef, DirectiveBinding} from './element_injector';
import {AppView} from './view';
/**
@ -43,13 +42,15 @@ export class ComponentRef {
export class DynamicComponentLoader {
_compiler:Compiler;
_viewFactory:ViewFactory;
_viewHydrator:AppViewHydrator;
_directiveMetadataReader:DirectiveMetadataReader;
constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader,
renderer:Renderer, viewFactory:ViewFactory) {
viewFactory:ViewFactory, viewHydrator:AppViewHydrator) {
this._compiler = compiler;
this._directiveMetadataReader = directiveMetadataReader;
this._viewFactory = viewFactory
this._viewFactory = viewFactory;
this._viewHydrator = viewHydrator;
}
/**
@ -58,61 +59,44 @@ export class DynamicComponentLoader {
*/
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);
var directiveMetadata = this._directiveMetadataReader.read(type);
var inj = this._componentAppInjector(location, injector, directiveMetadata.resolvedInjectables);
var hostEi = location.elementInjector;
var hostView = location.hostView;
return this._compiler.compile(type).then(componentProtoView => {
var component = hostEi.dynamicallyCreateComponent(type, directiveMetadata.annotation, inj);
var componentView = this._instantiateAndHydrateView(componentProtoView, injector, hostEi, component);
//TODO(vsavkin): do not use component child views as we need to clear the dynamically created views
//same problem exists on the render side
hostView.setDynamicComponentChildView(location.boundElementIndex, componentView);
var componentView = this._viewFactory.getView(componentProtoView);
var hostView = location.hostView;
this._viewHydrator.hydrateDynamicComponentView(
hostView, location.boundElementIndex, componentView, componentBinding, injector);
// TODO(vsavkin): return a component ref that dehydrates the component view and removes it
// from the component child views
return new ComponentRef(location, component, componentView);
// See ViewFactory.returnView
// See AppViewHydrator.dehydrateDynamicComponentView
return new ComponentRef(location, location.elementInjector.getDynamicallyLoadedComponent(), componentView);
});
}
/**
* Loads a component as a child of the View given by the provided ElementRef. The loaded
* component receives injection normally as a hosted view.
*
* TODO(vsavkin, jelbourn): remove protoViewFactory after render layer exists.
*/
loadIntoNewLocation(elementOrSelector:any, type:Type, location:ElementRef,
injector:Injector = null):Promise<ComponentRef> {
this._assertTypeIsComponent(type);
var inj = this._componentAppInjector(location, injector, null);
//TODO(tbosch) this should always be a selector
return this._compiler.compileRoot(elementOrSelector, type).then(pv => {
var hostView = this._instantiateAndHydrateView(pv, inj, null, new Object());
return this._compiler.compileInHost(type).then(hostProtoView => {
var hostView = this._viewFactory.getView(hostProtoView);
this._viewHydrator.hydrateInPlaceHostView(null, elementOrSelector, hostView, injector);
// TODO(vsavkin): return a component ref that dehydrates the host view
// See ViewFactory.returnView
// See AppViewHydrator.dehydrateInPlaceHostView
var newLocation = new ElementRef(hostView.elementInjectors[0]);
var component = hostView.elementInjectors[0].getComponent();
return new ComponentRef(newLocation, component, hostView.componentChildViews[0]);
});
}
_componentAppInjector(location, injector:Injector, resolvedBindings:List<ResolvedBinding>) {
var inj = isPresent(injector) ? injector : location.injector;
return isPresent(resolvedBindings) ? inj.createChildFromResolved(resolvedBindings) : inj;
}
_instantiateAndHydrateView(protoView, injector, hostElementInjector, context) {
var componentView = this._viewFactory.getView(protoView);
componentView.hydrate(injector, hostElementInjector, context, null);
return componentView;
}
/** Asserts that the type being dynamically instantiated is a Component. */
_assertTypeIsComponent(type:Type) {
var annotation = this._directiveMetadataReader.read(type).annotation;

View File

@ -575,9 +575,9 @@ export class ElementInjector extends TreeNode {
if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);
}
dynamicallyCreateComponent(componentType:Type, annotation:Directive, injector:Injector) {
dynamicallyCreateComponent(directiveBinding, injector:Injector) {
this._shadowDomAppInjector = injector;
this._dynamicallyCreatedComponentBinding = DirectiveBinding.createFromType(componentType, annotation);
this._dynamicallyCreatedComponentBinding = directiveBinding;
this._dynamicallyCreatedComponent = this._new(this._dynamicallyCreatedComponentBinding);
return this._dynamicallyCreatedComponent;
}
@ -708,7 +708,7 @@ export class ElementInjector extends TreeNode {
_buildPropSetter(dep) {
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
var renderer = view.proto.renderer;
var renderer = view.renderer;
var index = this._proto.index;
return function(v) {
renderer.setElementProperty(view.render, index, dep.propSetterName, v);

View File

@ -13,11 +13,9 @@ import {ProtoElementInjector, DirectiveBinding} from './element_injector';
@Injectable()
export class ProtoViewFactory {
_changeDetection:ChangeDetection;
_renderer:renderApi.Renderer;
constructor(changeDetection:ChangeDetection, renderer:renderApi.Renderer) {
constructor(changeDetection:ChangeDetection) {
this._changeDetection = changeDetection;
this._renderer = renderer;
}
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto, directives:List<DirectiveBinding>):AppProtoView {
@ -30,7 +28,7 @@ export class ProtoViewFactory {
'dummy', componentAnnotation.changeDetection
);
}
var protoView = new AppProtoView(this._renderer, renderProtoView.render, protoChangeDetector);
var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector);
for (var i=0; i<renderProtoView.elementBinders.length; i++) {
var renderElementBinder = renderProtoView.elementBinders[i];

View File

@ -6,7 +6,6 @@ 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 {Injector} from 'angular2/di';
import {ViewContainer} from './view_container';
import * as renderApi from 'angular2/src/render/api';
@ -18,6 +17,7 @@ import * as renderApi from 'angular2/src/render/api';
// TODO(tbosch): this is not supported in dart2js (no '.' is allowed)
// @IMPLEMENTS(renderApi.EventDispatcher)
export class AppView {
render:renderApi.ViewRef;
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
rootElementInjectors:List<ElementInjector>;
@ -27,6 +27,7 @@ export class AppView {
viewContainers: List<ViewContainer>;
preBuiltObjects: List<PreBuiltObjects>;
proto: AppProtoView;
renderer: renderApi.Renderer;
/**
* The context against which data-binding expressions in this view are evaluated against.
@ -42,7 +43,7 @@ export class AppView {
*/
locals:Locals;
constructor(proto:AppProtoView, protoLocals:Map) {
constructor(renderer:renderApi.Renderer, proto:AppProtoView, protoLocals:Map) {
this.render = null;
this.proto = proto;
this.changeDetector = null;
@ -53,6 +54,7 @@ export class AppView {
this.preBuiltObjects = null;
this.context = null;
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
this.renderer = renderer;
}
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List,
@ -78,157 +80,6 @@ export class AppView {
return isPresent(this.context);
}
_setContextAndLocals(newContext, locals) {
this.context = newContext;
this.locals.parent = locals;
}
_hydrateChangeDetector() {
this.changeDetector.hydrate(this.context, this.locals, this);
}
_dehydrateContext() {
if (isPresent(this.locals)) {
this.locals.clearValues();
}
this.context = null;
this.changeDetector.dehydrate();
}
/**
* 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 does not attach/detach the view from the view
* tree.
*/
hydrate(appInjector: Injector, hostElementInjector: ElementInjector,
context: Object, locals:Locals) {
var renderComponentViewRefs = this.proto.renderer.createView(this.proto.render);
this.internalHydrateRecurse(renderComponentViewRefs, 0, appInjector, hostElementInjector, context, locals);
}
dehydrate() {
var render = this.render;
this.internalDehydrateRecurse();
this.proto.renderer.destroyView(render);
}
internalHydrateRecurse(
renderComponentViewRefs:List<renderApi.ViewRef>,
renderComponentIndex:number,
appInjector: Injector, hostElementInjector: ElementInjector,
context: Object, locals:Locals):number {
if (this.hydrated()) throw new BaseException('The view is already hydrated.');
this.render = renderComponentViewRefs[renderComponentIndex++];
this._setContextAndLocals(context, locals);
// viewContainers
for (var i = 0; i < this.viewContainers.length; i++) {
var vc = this.viewContainers[i];
if (isPresent(vc)) {
vc.internalHydrateRecurse(new renderApi.ViewContainerRef(this.render, i), appInjector, hostElementInjector);
}
}
var binders = this.proto.elementBinders;
for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective;
var shadowDomAppInjector = null;
// shadowDomAppInjector
if (isPresent(componentDirective)) {
var injectables = componentDirective.resolvedInjectables;
if (isPresent(injectables))
shadowDomAppInjector = appInjector.createChildFromResolved(injectables);
else {
shadowDomAppInjector = appInjector;
}
} else {
shadowDomAppInjector = null;
}
// elementInjectors
var elementInjector = this.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, this.preBuiltObjects[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()) {
this.locals.set(exportImplicitName, elementInjector.getComponent());
} else if (elementInjector.isExportingElement()) {
this.locals.set(exportImplicitName, elementInjector.getNgElement());
}
}
if (binders[i].hasStaticComponent()) {
renderComponentIndex = this.componentChildViews[i].internalHydrateRecurse(
renderComponentViewRefs,
renderComponentIndex,
shadowDomAppInjector,
elementInjector,
elementInjector.getComponent(),
null
);
}
}
this._hydrateChangeDetector();
this.proto.renderer.setEventDispatcher(this.render, this);
return renderComponentIndex;
}
internalDehydrateRecurse() {
// Note: preserve the opposite order of the hydration process.
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
var componentView = this.componentChildViews[i];
if (isPresent(componentView)) {
componentView.internalDehydrateRecurse();
var binder = this.proto.elementBinders[i];
if (binder.hasDynamicComponent()) {
this.componentChildViews[i] = null;
this.changeDetector.removeShadowDomChild(componentView.changeDetector);
}
}
}
// elementInjectors
for (var i = 0; i < this.elementInjectors.length; i++) {
if (isPresent(this.elementInjectors[i])) {
this.elementInjectors[i].clearDirectives();
}
}
// viewContainers
if (isPresent(this.viewContainers)) {
for (var i = 0; i < this.viewContainers.length; i++) {
var vc = this.viewContainers[i];
if (isPresent(vc)) {
vc.internalDehydrateRecurse();
}
}
}
this.render = null;
this._dehydrateContext();
}
/**
* Triggers the event handlers for the element and the directives.
*
@ -247,12 +98,12 @@ export class AppView {
// dispatch to element injector or text nodes based on context
notifyOnBinding(b:BindingRecord, currentValue:any) {
if (b.isElement()) {
this.proto.renderer.setElementProperty(
this.renderer.setElementProperty(
this.render, b.elementIndex, b.propertyName, currentValue
);
} else {
// we know it refers to _textNodes.
this.proto.renderer.setText(this.render, b.elementIndex, currentValue);
this.renderer.setText(this.render, b.elementIndex, currentValue);
}
}
@ -266,18 +117,6 @@ export class AppView {
return elementInjector.getChangeDetector();
}
setDynamicComponentChildView(boundElementIndex, view:AppView) {
if (!this.proto.elementBinders[boundElementIndex].hasDynamicComponent()) {
throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`);
}
if (isPresent(this.componentChildViews[boundElementIndex])) {
throw new BaseException(`There already is a bound component at element ${boundElementIndex}`);
}
this.componentChildViews[boundElementIndex] = view;
this.changeDetector.addShadowDomChild(view.changeDetector);
this.proto.renderer.setDynamicComponentView(this.render, boundElementIndex, view.render);
}
// implementation of EventDispatcher#dispatchEvent
dispatchEvent(
elementIndex:number, eventName:string, locals:Map<string, any>
@ -319,13 +158,10 @@ export class AppProtoView {
_directiveRecordsMap:Map;
_directiveRecords:List;
render:renderApi.ProtoViewRef;
renderer:renderApi.Renderer;
constructor(
renderer:renderApi.Renderer,
render:renderApi.ProtoViewRef,
protoChangeDetector:ProtoChangeDetector) {
this.renderer = renderer;
this.render = render;
this.elementBinders = [];
this.variableBindings = MapWrapper.create();

View File

@ -7,13 +7,18 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
import * as renderApi from 'angular2/src/render/api';
import * as viewModule from './view';
import * as vfModule from './view_factory';
import * as vhModule from './view_hydrator';
import {Renderer} from 'angular2/src/render/api';
/**
* @exportedAs angular2/template
*/
export class ViewContainer {
render:renderApi.ViewContainerRef;
viewFactory: vfModule.ViewFactory;
viewHydrator: vhModule.AppViewHydrator;
renderer: Renderer;
render:renderApi.ViewContainerRef;
parentView: viewModule.AppView;
defaultProtoView: viewModule.AppProtoView;
_views: List<viewModule.AppView>;
@ -22,10 +27,13 @@ export class ViewContainer {
hostElementInjector: eiModule.ElementInjector;
constructor(viewFactory:vfModule.ViewFactory,
renderer: Renderer,
parentView: viewModule.AppView,
defaultProtoView: viewModule.AppProtoView,
elementInjector: eiModule.ElementInjector) {
this.viewFactory = viewFactory;
this.viewHydrator = null;
this.renderer = renderer;
this.render = null;
this.parentView = parentView;
this.defaultProtoView = defaultProtoView;
@ -37,28 +45,10 @@ export class ViewContainer {
this.hostElementInjector = null;
}
internalHydrateRecurse(render:renderApi.ViewContainerRef, appInjector: Injector, hostElementInjector: eiModule.ElementInjector) {
this.render = render;
this.appInjector = appInjector;
this.hostElementInjector = hostElementInjector;
}
internalDehydrateRecurse() {
this.appInjector = null;
this.hostElementInjector = null;
this.render = null;
// Note: We don't call clear here,
// as we don't want to change the render side
// (i.e. don't deattach views on the render side),
// as the render side does its own recursion.
internalClearWithoutRender() {
for (var i = this._views.length - 1; i >= 0; i--) {
var view = this._views[i];
view.changeDetector.remove();
this._unlinkElementInjectors(view);
view.internalDehydrateRecurse();
this.viewFactory.returnView(view);
this._detachInjectors(i);
}
this._views = [];
}
clear() {
@ -86,31 +76,31 @@ export class ViewContainer {
// TODO(rado): profile and decide whether bounds checks should be added
// to the methods below.
create(atIndex=-1): viewModule.AppView {
create(atIndex=-1, protoView:viewModule.AppProtoView = null): viewModule.AppView {
if (atIndex == -1) atIndex = this._views.length;
if (!this.hydrated()) throw new BaseException(
'Cannot create views on a dehydrated ViewContainer');
var newView = this.viewFactory.getView(this.defaultProtoView);
if (isBlank(protoView)) {
protoView = this.defaultProtoView;
}
var newView = this.viewFactory.getView(protoView);
// insertion must come before hydration so that element injector trees are attached.
this._insertWithoutRender(newView, atIndex);
// hydration must come before changing the render side,
// as it acquires the render views.
newView.hydrate(this.appInjector, this.hostElementInjector,
this.parentView.context, this.parentView.locals);
this.defaultProtoView.renderer.insertViewIntoContainer(this.render, newView.render, atIndex);
this._insertInjectors(newView, atIndex);
this.viewHydrator.hydrateViewInViewContainer(this, atIndex, newView);
return newView;
}
insert(view, atIndex=-1): viewModule.AppView {
this._insertWithoutRender(view, atIndex);
this.defaultProtoView.renderer.insertViewIntoContainer(this.render, view.render, atIndex);
if (atIndex == -1) atIndex = this._views.length;
this._insertInjectors(view, atIndex);
this.parentView.changeDetector.addChild(view.changeDetector);
this.renderer.insertViewIntoContainer(this.render, atIndex, view.render);
return view;
}
_insertWithoutRender(view, atIndex=-1): viewModule.AppView {
if (atIndex == -1) atIndex = this._views.length;
_insertInjectors(view, atIndex): viewModule.AppView {
ListWrapper.insert(this._views, atIndex, view);
this.parentView.changeDetector.addChild(view.changeDetector);
this._linkElementInjectors(this._siblingInjectorToLinkAfter(atIndex), view);
return view;
@ -118,8 +108,10 @@ export class ViewContainer {
remove(atIndex=-1) {
if (atIndex == -1) atIndex = this._views.length - 1;
var view = this.detach(atIndex);
view.dehydrate();
var view = this._views[atIndex];
// opposite order as in create
this.viewHydrator.dehydrateViewInViewContainer(this, atIndex, view);
this._detachInjectors(atIndex);
this.viewFactory.returnView(view);
// view is intentionally not returned to the client.
}
@ -130,16 +122,17 @@ export class ViewContainer {
*/
detach(atIndex=-1): viewModule.AppView {
if (atIndex == -1) atIndex = this._views.length - 1;
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
this.defaultProtoView.renderer.detachViewFromContainer(this.render, atIndex);
var detachedView = this._detachInjectors(atIndex);
detachedView.changeDetector.remove();
this._unlinkElementInjectors(detachedView);
this.renderer.detachViewFromContainer(this.render, atIndex);
return detachedView;
}
contentTagContainers() {
return this._views;
_detachInjectors(atIndex): viewModule.AppView {
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
this._unlinkElementInjectors(detachedView);
return detachedView;
}
_linkElementInjectors(sibling, view) {

View File

@ -5,6 +5,7 @@ import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {NgElement} from 'angular2/src/core/compiler/ng_element';
import * as vcModule from './view_container';
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';
@ -13,10 +14,12 @@ export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity';
export class ViewFactory {
_poolCapacityPerProtoView:number;
_pooledViewsPerProtoView:Map<viewModule.AppProtoView, List<viewModule.AppView>>;
_renderer:Renderer;
constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView) {
constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, renderer:Renderer) {
this._poolCapacityPerProtoView = poolCapacityPerProtoView;
this._pooledViewsPerProtoView = MapWrapper.create();
this._renderer = renderer;
}
getView(protoView:viewModule.AppProtoView):viewModule.AppView {
@ -47,7 +50,7 @@ export class ViewFactory {
}
_createView(protoView:viewModule.AppProtoView): viewModule.AppView {
var view = new viewModule.AppView(protoView, protoView.protoLocals);
var view = new viewModule.AppView(this._renderer, protoView, protoView.protoLocals);
var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindings,
protoView.getVariableBindings(), protoView.getdirectiveRecords());
@ -81,14 +84,14 @@ export class ViewFactory {
var childView = this._createView(binder.nestedProtoView);
childChangeDetector = childView.changeDetector;
changeDetector.addShadowDomChild(childChangeDetector);
componentChildViews[binderIdx] = childView;
}
// viewContainers
var viewContainer = null;
if (isPresent(binder.viewportDirective)) {
viewContainer = new vcModule.ViewContainer(this, view, binder.nestedProtoView, elementInjector);
viewContainer = new vcModule.ViewContainer(this, this._renderer, view, binder.nestedProtoView, elementInjector);
}
viewContainers[binderIdx] = viewContainer;

View File

@ -0,0 +1,284 @@
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';
/**
* 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;
constructor(renderer:renderApi.Renderer) {
this._renderer = renderer;
}
hydrateDynamicComponentView(hostView:viewModule.AppView, boundElementIndex:number,
componentView:viewModule.AppView, componentDirective:eli.DirectiveBinding, injector:Injector) {
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)) {
// TODO: We should have another way of accesing the app injector at hostView place.
injector = new eli.ElementRef(hostElementInjector).injector;
}
// 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(parentView:viewModule.AppView, hostElementSelector, hostView:viewModule.AppView, injector:Injector) {
var parentRenderViewRef = null;
if (isPresent(parentView)) {
// Needed for user views
throw new BaseException('Not yet supported');
}
var binder = hostView.proto.elementBinders[0];
var shadowDomAppInjector = this._createShadowDomAppInjector(binder.componentDirective, injector);
// render views
var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostView.proto.render);
this._viewHydrateRecurse(
hostView, renderViewRefs, 0, shadowDomAppInjector, null, new Object(), null
);
}
dehydrateInPlaceHostView(parentView:viewModule.AppView, hostView:viewModule.AppView) {
var parentRenderViewRef = null;
if (isPresent(parentView)) {
// Needed for user views
throw new BaseException('Not yet supported');
}
var render = hostView.render;
this._viewDehydrateRecurse(hostView);
this._renderer.destroyInPlaceHostView(parentRenderViewRef, render);
}
hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView) {
if (!viewContainer.hydrated()) throw new BaseException(
'Cannot create views on a dehydrated ViewContainer');
var renderViewRefs = this._renderer.createViewInContainer(viewContainer.render, atIndex, view.proto.render);
viewContainer.parentView.changeDetector.addChild(view.changeDetector);
this._viewHydrateRecurse(view, renderViewRefs, 0, viewContainer.appInjector, viewContainer.hostElementInjector,
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.render, 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.render = renderComponentViewRefs[renderComponentIndex++];
view.context = context;
view.locals.parent = locals;
// viewContainers
for (var i = 0; i < view.viewContainers.length; i++) {
var vc = view.viewContainers[i];
if (isPresent(vc)) {
this._viewContainerHydrateRecurse(vc, new renderApi.ViewContainerRef(view.render, i), appInjector, hostElementInjector);
}
}
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]);
// 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());
}
}
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;
}
/**
* 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.componentChildViews[i] = null;
view.changeDetector.removeShadowDomChild(componentView.changeDetector);
}
}
}
// 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.
*/
_viewContainerHydrateRecurse(viewContainer:vcModule.ViewContainer, render:renderApi.ViewContainerRef, appInjector: Injector, hostElementInjector: eli.ElementInjector) {
viewContainer.viewHydrator = this;
viewContainer.render = render;
viewContainer.appInjector = appInjector;
viewContainer.hostElementInjector = hostElementInjector;
}
/**
* 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();
viewContainer.viewHydrator = null;
viewContainer.appInjector = null;
viewContainer.hostElementInjector = null;
viewContainer.render = null;
}
}