Skip to content

Commit bfa381b

Browse files
committed
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 angular#1477
1 parent f784063 commit bfa381b

27 files changed

+1514
-1216
lines changed

modules/angular2/src/core/application.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_res
2828
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
2929
import {ComponentRef, DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
3030
import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability';
31-
import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory';
32-
import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator';
31+
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
32+
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
33+
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
3334
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
3435
import {Renderer} from 'angular2/src/render/api';
3536
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
@@ -105,12 +106,13 @@ function _injectorBindings(appComponentType): List<Binding> {
105106
ProtoViewFactory,
106107
// TODO(tbosch): We need an explicit factory here, as
107108
// we are getting errors in dart2js with mirrors...
108-
bind(ViewFactory).toFactory(
109-
(capacity, renderer) => new ViewFactory(capacity, renderer),
110-
[VIEW_POOL_CAPACITY, Renderer]
109+
bind(AppViewPool).toFactory(
110+
(capacity) => new AppViewPool(capacity),
111+
[APP_VIEW_POOL_CAPACITY]
111112
),
112-
bind(VIEW_POOL_CAPACITY).toValue(10000),
113-
AppViewHydrator,
113+
bind(APP_VIEW_POOL_CAPACITY).toValue(10000),
114+
AppViewManager,
115+
AppViewManagerUtils,
114116
Compiler,
115117
CompilerCache,
116118
TemplateResolver,

modules/angular2/src/core/compiler/compiler.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,17 @@ export class Compiler {
8888
// Create a hostView as if the compiler encountered <hostcmp></hostcmp>.
8989
// Used for bootstrapping.
9090
compileInHost(componentTypeOrBinding:any):Promise<AppProtoView> {
91+
var componentBinding = this._bindDirective(componentTypeOrBinding);
92+
this._assertTypeIsComponent(componentBinding);
9193
return this._renderer.createHostProtoView('host').then( (hostRenderPv) => {
92-
return this._compileNestedProtoViews(null, hostRenderPv, [this._bindDirective(componentTypeOrBinding)], true);
94+
return this._compileNestedProtoViews(null, hostRenderPv, [componentBinding], true);
9395
});
9496
}
9597

9698
compile(component: Type):Promise<AppProtoView> {
97-
var protoView = this._compile(this._bindDirective(component));
99+
var componentBinding = this._bindDirective(component);
100+
this._assertTypeIsComponent(componentBinding);
101+
var protoView = this._compile(componentBinding);
98102
return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
99103
}
100104

@@ -265,4 +269,9 @@ export class Compiler {
265269
}
266270
}
267271

272+
_assertTypeIsComponent(directiveBinding:DirectiveBinding):void {
273+
if (!(directiveBinding.annotation instanceof Component)) {
274+
throw new BaseException(`Could not load '${stringify(directiveBinding.key.token)}' because it is not a component.`);
275+
}
276+
}
268277
}

modules/angular2/src/core/compiler/dynamic_component_loader.js

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import {Key, Injector, Injectable, ResolvedBinding} from 'angular2/di'
1+
import {Key, Injector, Injectable, ResolvedBinding, Binding, bind} from 'angular2/di'
22
import {Compiler} from './compiler';
3-
import {DirectiveMetadataReader} from './directive_metadata_reader';
43
import {Type, BaseException, stringify, isPresent} from 'angular2/src/facade/lang';
54
import {Promise} from 'angular2/src/facade/async';
6-
import {Component} from 'angular2/src/core/annotations/annotations';
7-
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
8-
import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator';
9-
import {ElementRef, DirectiveBinding} from './element_injector';
5+
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
6+
import {ElementRef} from './element_injector';
107
import {AppView} from './view';
118

129
/**
@@ -47,31 +44,23 @@ export class ComponentRef {
4744
@Injectable()
4845
export class DynamicComponentLoader {
4946
_compiler:Compiler;
50-
_viewFactory:ViewFactory;
51-
_viewHydrator:AppViewHydrator;
52-
_directiveMetadataReader:DirectiveMetadataReader;
47+
_viewManager:AppViewManager;
5348

54-
constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader,
55-
viewFactory:ViewFactory, viewHydrator:AppViewHydrator) {
49+
constructor(compiler:Compiler,
50+
viewManager: AppViewManager) {
5651
this._compiler = compiler;
57-
this._directiveMetadataReader = directiveMetadataReader;
58-
this._viewFactory = viewFactory;
59-
this._viewHydrator = viewHydrator;
52+
this._viewManager = viewManager;
6053
}
6154

6255
/**
6356
* Loads a component into the location given by the provided ElementRef. The loaded component
6457
* receives injection as if it in the place of the provided ElementRef.
6558
*/
66-
loadIntoExistingLocation(type:Type, location:ElementRef, injector:Injector = null):Promise<ComponentRef> {
67-
this._assertTypeIsComponent(type);
68-
var annotation = this._directiveMetadataReader.read(type).annotation;
69-
var componentBinding = DirectiveBinding.createFromType(type, annotation);
70-
71-
return this._compiler.compile(type).then(componentProtoView => {
72-
var componentView = this._viewFactory.getView(componentProtoView);
73-
this._viewHydrator.hydrateDynamicComponentView(
74-
location, componentView, componentBinding, injector);
59+
loadIntoExistingLocation(typeOrBinding, location:ElementRef, injector:Injector = null):Promise<ComponentRef> {
60+
var binding = this._getBinding(typeOrBinding);
61+
return this._compiler.compile(binding.token).then(componentProtoView => {
62+
var componentView = this._viewManager.createDynamicComponentView(
63+
location, componentProtoView, binding, injector);
7564

7665
var dispose = () => {throw new BaseException("Not implemented");};
7766
return new ComponentRef(location, location.elementInjector.getDynamicallyLoadedComponent(), componentView, dispose);
@@ -82,21 +71,16 @@ export class DynamicComponentLoader {
8271
* Loads a component in the element specified by elementOrSelector. The loaded component receives
8372
* injection normally as a hosted view.
8473
*/
85-
loadIntoNewLocation(type:Type, parentComponentLocation:ElementRef, elementOrSelector:any,
74+
loadIntoNewLocation(typeOrBinding, parentComponentLocation:ElementRef, elementOrSelector:any,
8675
injector:Injector = null):Promise<ComponentRef> {
87-
this._assertTypeIsComponent(type);
88-
89-
return this._compiler.compileInHost(type).then(hostProtoView => {
90-
var hostView = this._viewFactory.getView(hostProtoView);
91-
this._viewHydrator.hydrateInPlaceHostView(
92-
parentComponentLocation, elementOrSelector, hostView, injector
93-
);
76+
return this._compiler.compileInHost(this._getBinding(typeOrBinding)).then(hostProtoView => {
77+
var hostView = this._viewManager.createInPlaceHostView(
78+
parentComponentLocation, elementOrSelector, hostProtoView, injector);
9479

9580
var newLocation = hostView.elementInjectors[0].getElementRef();
9681
var component = hostView.elementInjectors[0].getComponent();
9782
var dispose = () => {
98-
this._viewHydrator.dehydrateInPlaceHostView(parentComponentLocation, hostView);
99-
this._viewFactory.returnView(hostView);
83+
this._viewManager.destroyInPlaceHostView(parentComponentLocation, hostView);
10084
};
10185
return new ComponentRef(newLocation, component, hostView.componentChildViews[0], dispose);
10286
});
@@ -106,10 +90,9 @@ export class DynamicComponentLoader {
10690
* Loads a component next to the provided ElementRef. The loaded component receives
10791
* injection normally as a hosted view.
10892
*/
109-
loadNextToExistingLocation(type:Type, location:ElementRef, injector:Injector = null):Promise<ComponentRef> {
110-
this._assertTypeIsComponent(type);
111-
112-
return this._compiler.compileInHost(type).then(hostProtoView => {
93+
loadNextToExistingLocation(typeOrBinding, location:ElementRef, injector:Injector = null):Promise<ComponentRef> {
94+
var binding = this._getBinding(typeOrBinding);
95+
return this._compiler.compileInHost(binding).then(hostProtoView => {
11396
var hostView = location.viewContainer.create(-1, hostProtoView, injector);
11497

11598
var newLocation = hostView.elementInjectors[0].getElementRef();
@@ -122,11 +105,14 @@ export class DynamicComponentLoader {
122105
});
123106
}
124107

125-
/** Asserts that the type being dynamically instantiated is a Component. */
126-
_assertTypeIsComponent(type:Type):void {
127-
var annotation = this._directiveMetadataReader.read(type).annotation;
128-
if (!(annotation instanceof Component)) {
129-
throw new BaseException(`Could not load '${stringify(type)}' because it is not a component.`);
108+
_getBinding(typeOrBinding) {
109+
var binding;
110+
if (typeOrBinding instanceof Binding) {
111+
binding = typeOrBinding;
112+
} else {
113+
binding = bind(typeOrBinding).toClass(typeOrBinding);
130114
}
115+
return binding;
131116
}
117+
132118
}

modules/angular2/src/core/compiler/element_injector.js

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingErro
77
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
88
import {Attribute, Query} from 'angular2/src/core/annotations/di';
99
import * as viewModule from 'angular2/src/core/compiler/view';
10+
import * as avmModule from './view_manager';
1011
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
1112
import {NgElement} from 'angular2/src/core/compiler/ng_element';
1213
import {Directive, Component, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
@@ -30,28 +31,30 @@ export class ElementRef {
3031
boundElementIndex:number;
3132
injector:Injector;
3233
elementInjector:ElementInjector;
34+
viewContainer:ViewContainer;
3335

34-
constructor(elementInjector, hostView, boundElementIndex, injector){
36+
constructor(elementInjector, hostView, boundElementIndex, injector, viewManager, defaultProtoView){
3537
this.elementInjector = elementInjector;
3638
this.hostView = hostView;
3739
this.boundElementIndex = boundElementIndex;
3840
this.injector = injector;
39-
}
40-
41-
get viewContainer() {
42-
return this.hostView.getOrCreateViewContainer(this.boundElementIndex);
41+
this.viewContainer = new ViewContainer(viewManager, this, defaultProtoView);
4342
}
4443
}
4544

4645
class StaticKeys {
46+
viewManagerId:number;
4747
viewId:number;
4848
ngElementId:number;
49+
defaultProtoViewId:number;
4950
viewContainerId:number;
5051
changeDetectorRefId:number;
5152
elementRefId:number;
5253

5354
constructor() {
5455
//TODO: vsavkin Key.annotate(Key.get(AppView), 'static')
56+
this.viewManagerId = Key.get(avmModule.AppViewManager).id;
57+
this.defaultProtoViewId = Key.get(viewModule.AppProtoView).id;
5558
this.viewId = Key.get(viewModule.AppView).id;
5659
this.ngElementId = Key.get(NgElement).id;
5760
this.viewContainerId = Key.get(ViewContainer).id;
@@ -290,13 +293,15 @@ export class DirectiveBinding extends ResolvedBinding {
290293

291294
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
292295
export class PreBuiltObjects {
296+
viewManager:avmModule.AppViewManager;
297+
defaultProtoView:viewModule.AppProtoView;
293298
view:viewModule.AppView;
294299
element:NgElement;
295-
changeDetector:ChangeDetector;
296-
constructor(view, element:NgElement, changeDetector:ChangeDetector) {
300+
constructor(viewManager:avmModule.AppViewManager, view:viewModule.AppView, element:NgElement, defaultProtoView:viewModule.AppProtoView) {
301+
this.viewManager = viewManager;
297302
this.view = view;
303+
this.defaultProtoView = defaultProtoView;
298304
this.element = element;
299-
this.changeDetector = changeDetector;
300305
}
301306
}
302307

@@ -649,10 +654,6 @@ export class ElementInjector extends TreeNode {
649654
return this._preBuiltObjects.element;
650655
}
651656

652-
getChangeDetector() {
653-
return this._preBuiltObjects.changeDetector;
654-
}
655-
656657
getComponent() {
657658
if (this._proto._binding0IsComponent) {
658659
return this._obj0;
@@ -662,7 +663,8 @@ export class ElementInjector extends TreeNode {
662663
}
663664

664665
getElementRef() {
665-
return new ElementRef(this, this._preBuiltObjects.view, this._proto.index, this._lightDomAppInjector);
666+
return new ElementRef(this, this._preBuiltObjects.view, this._proto.index, this._lightDomAppInjector,
667+
this._preBuiltObjects.viewManager, this._preBuiltObjects.defaultProtoView);
666668
}
667669

668670
getDynamicallyLoadedComponent() {
@@ -732,9 +734,16 @@ export class ElementInjector extends TreeNode {
732734
_getByDependency(dep:DirectiveDependency, requestor:Key) {
733735
if (isPresent(dep.attributeName)) return this._buildAttribute(dep);
734736
if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list;
737+
if (dep.key.id === StaticKeys.instance().changeDetectorRefId) {
738+
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
739+
return componentView.changeDetector.ref;
740+
}
735741
if (dep.key.id === StaticKeys.instance().elementRefId) {
736742
return this.getElementRef();
737743
}
744+
if (dep.key.id === StaticKeys.instance().viewContainerId) {
745+
return this.getElementRef().viewContainer;
746+
}
738747
return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
739748
}
740749

@@ -906,10 +915,10 @@ export class ElementInjector extends TreeNode {
906915
_getPreBuiltObjectByKeyId(keyId:int) {
907916
var staticKeys = StaticKeys.instance();
908917
// TODO: AppView should not be injectable. Remove it.
918+
if (keyId === staticKeys.viewManagerId) return this._preBuiltObjects.viewManagerId;
909919
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
910920
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
911-
if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.view.getOrCreateViewContainer(this._proto.index);
912-
if (keyId === staticKeys.changeDetectorRefId) return this._preBuiltObjects.changeDetector.ref;
921+
if (keyId === staticKeys.defaultProtoViewId) return this._preBuiltObjects.defaultProtoView;
913922

914923
//TODO add other objects as needed
915924
return _undefined;
@@ -968,6 +977,10 @@ export class ElementInjector extends TreeNode {
968977
return this._lightDomAppInjector;
969978
}
970979

980+
getShadowDomAppInjector() {
981+
return this._shadowDomAppInjector;
982+
}
983+
971984
getHost() {
972985
return this._host;
973986
}

modules/angular2/src/core/compiler/view.js

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@ import {ProtoElementInjector, ElementInjector, PreBuiltObjects, DirectiveBinding
66
import {ElementBinder} from './element_binder';
77
import {SetterFn} from 'angular2/src/reflection/types';
88
import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
9-
import {ViewContainer} from './view_container';
109
import * as renderApi from 'angular2/src/render/api';
11-
import * as vfModule from './view_factory';
12-
import * as vhModule from './view_hydrator';
10+
11+
// TODO(tbosch): rename ViewContainer -> ViewContainerRef
12+
// and InternalAppViewContainer -> ViewContainer!
13+
export class InternalAppViewContainer {
14+
views: List<AppView>;
15+
16+
constructor() {
17+
// The order in this list matches the DOM order.
18+
this.views = [];
19+
}
20+
}
1321

1422
/**
1523
* Const of making objects: http://jsperf.com/instantiate-size-of-object
@@ -28,12 +36,10 @@ export class AppView {
2836
/// Host views that were added by an imperative view.
2937
/// This is a dynamically growing / shrinking array.
3038
imperativeHostViews: List<AppView>;
31-
viewContainers: List<ViewContainer>;
39+
viewContainers: List<InternalAppViewContainer>;
3240
preBuiltObjects: List<PreBuiltObjects>;
3341
proto: AppProtoView;
3442
renderer: renderApi.Renderer;
35-
viewFactory: vfModule.ViewFactory;
36-
viewHydrator: vhModule.AppViewHydrator;
3743

3844
/**
3945
* The context against which data-binding expressions in this view are evaluated against.
@@ -49,7 +55,7 @@ export class AppView {
4955
*/
5056
locals:Locals;
5157

52-
constructor(renderer:renderApi.Renderer, viewFactory:vfModule.ViewFactory, proto:AppProtoView, protoLocals:Map) {
58+
constructor(renderer:renderApi.Renderer, proto:AppProtoView, protoLocals:Map) {
5359
this.render = null;
5460
this.proto = proto;
5561
this.changeDetector = null;
@@ -61,8 +67,6 @@ export class AppView {
6167
this.context = null;
6268
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
6369
this.renderer = renderer;
64-
this.viewFactory = viewFactory;
65-
this.viewHydrator = null;
6670
this.imperativeHostViews = [];
6771
}
6872

@@ -75,15 +79,6 @@ export class AppView {
7579
this.componentChildViews = componentChildViews;
7680
}
7781

78-
getOrCreateViewContainer(boundElementIndex:number):ViewContainer {
79-
var viewContainer = this.viewContainers[boundElementIndex];
80-
if (isBlank(viewContainer)) {
81-
viewContainer = new ViewContainer(this, this.proto.elementBinders[boundElementIndex].nestedProtoView, this.elementInjectors[boundElementIndex]);
82-
this.viewContainers[boundElementIndex] = viewContainer;
83-
}
84-
return viewContainer;
85-
}
86-
8782
setLocal(contextName: string, value):void {
8883
if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.');
8984
if (!MapWrapper.contains(this.proto.variableBindings, contextName)) {
@@ -130,8 +125,8 @@ export class AppView {
130125
}
131126

132127
getDetectorFor(directive:DirectiveIndex) {
133-
var elementInjector = this.elementInjectors[directive.elementIndex];
134-
return elementInjector.getChangeDetector();
128+
var childView = this.componentChildViews[directive.elementIndex];
129+
return isPresent(childView) ? childView.changeDetector : null;
135130
}
136131

137132
// implementation of EventDispatcher#dispatchEvent

0 commit comments

Comments
 (0)