Skip to content

Commit 421d891

Browse files
committed
refactor(view_manager): split inPlace views into root and free host views.
BREAKING CHANGE: `AppViewManager.createInPlaceHostView` is replaced by `AppViewManager.createRootHostView` (for bootstrap) and `AppViewManager.createFreeHostView` (for imperative components). The later creates new host elements that are not attached anywhere. To attach them, use `DomRenderer.getHostElement(hostviewRef)` to get the host element. Closes #1920
1 parent a38a0d6 commit 421d891

File tree

14 files changed

+309
-157
lines changed

14 files changed

+309
-157
lines changed

Diff for: modules/angular2/src/core/application.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,18 @@ function _injectorBindings(appComponentType): List<Binding> {
5454
return [
5555
bind(DOCUMENT_TOKEN).toValue(DOM.defaultDoc()),
5656
bind(appComponentRefToken).toAsyncFactory((dynamicComponentLoader, injector,
57-
metadataReader, testability, registry) => {
57+
testability, registry) => {
5858

59-
var annotation = metadataReader.resolve(appComponentType);
60-
61-
var selector = annotation.selector;
6259
// TODO(rado): investigate whether to support bindings on root component.
63-
return dynamicComponentLoader.loadIntoNewLocation(appComponentType, null, selector, injector).then( (componentRef) => {
60+
return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector).then( (componentRef) => {
6461
var domView = resolveInternalDomView(componentRef.hostView.render);
6562
// We need to do this here to ensure that we create Testability and
6663
// it's ready on the window for users.
6764
registry.registerApplication(domView.boundElements[0], testability);
6865

6966
return componentRef;
7067
});
71-
}, [DynamicComponentLoader, Injector, DirectiveResolver,
68+
}, [DynamicComponentLoader, Injector,
7269
Testability, TestabilityRegistry]),
7370

7471
bind(appComponentType).toFactory((ref) => ref.instance,

Diff for: modules/angular2/src/core/compiler/dynamic_component_loader.js

+25-6
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,38 @@ export class DynamicComponentLoader {
6262
}
6363

6464
/**
65-
* Loads a component in the element specified by elementSelector. The loaded component receives
66-
* injection normally as a hosted view.
65+
* Loads a root component that is placed at the first element that matches the
66+
* component's selector.
67+
* The loaded component receives injection normally as a hosted view.
68+
*/
69+
loadAsRoot(typeOrBinding, overrideSelector = null, injector:Injector = null):Promise<ComponentRef> {
70+
return this._compiler.compileInHost(this._getBinding(typeOrBinding)).then(hostProtoViewRef => {
71+
var hostViewRef = this._viewManager.createRootHostView(hostProtoViewRef, overrideSelector, injector);
72+
var newLocation = new ElementRef(hostViewRef, 0);
73+
var component = this._viewManager.getComponent(newLocation);
74+
75+
var dispose = () => {
76+
this._viewManager.destroyRootHostView(hostViewRef);
77+
};
78+
return new ComponentRef(newLocation, component, dispose);
79+
});
80+
}
81+
82+
/**
83+
* Loads a component into a free host view that is not yet attached to
84+
* a parent on the render side, although it is attached to a parent in the injector hierarchy.
85+
* The loaded component receives injection normally as a hosted view.
6786
*/
68-
loadIntoNewLocation(typeOrBinding, parentComponentLocation:ElementRef, elementSelector:string,
87+
loadIntoNewLocation(typeOrBinding, parentComponentLocation:ElementRef,
6988
injector:Injector = null):Promise<ComponentRef> {
7089
return this._compiler.compileInHost(this._getBinding(typeOrBinding)).then(hostProtoViewRef => {
71-
var hostViewRef = this._viewManager.createInPlaceHostView(
72-
parentComponentLocation, elementSelector, hostProtoViewRef, injector);
90+
var hostViewRef = this._viewManager.createFreeHostView(
91+
parentComponentLocation, hostProtoViewRef, injector);
7392
var newLocation = new ElementRef(hostViewRef, 0);
7493
var component = this._viewManager.getComponent(newLocation);
7594

7695
var dispose = () => {
77-
this._viewManager.destroyInPlaceHostView(parentComponentLocation, hostViewRef);
96+
this._viewManager.destroyFreeHostView(parentComponentLocation, hostViewRef);
7897
};
7998
return new ComponentRef(newLocation, component, dispose);
8099
});

Diff for: modules/angular2/src/core/compiler/view.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class AppView {
3232
componentChildViews: List<AppView>;
3333
/// Host views that were added by an imperative view.
3434
/// This is a dynamically growing / shrinking array.
35-
inPlaceHostViews: List<AppView>;
35+
freeHostViews: List<AppView>;
3636
viewContainers: List<AppViewContainer>;
3737
preBuiltObjects: List<PreBuiltObjects>;
3838
proto: AppProtoView;
@@ -64,7 +64,7 @@ export class AppView {
6464
this.context = null;
6565
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
6666
this.renderer = renderer;
67-
this.inPlaceHostViews = [];
67+
this.freeHostViews = [];
6868
}
6969

7070
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List,

Diff for: modules/angular2/src/core/compiler/view_manager.js

+40-32
Original file line numberDiff line numberDiff line change
@@ -62,33 +62,46 @@ export class AppViewManager {
6262
return new ViewRef(componentView);
6363
}
6464

65-
createInPlaceHostView(parentComponentLocation:ElementRef,
66-
hostElementSelector:string, hostProtoViewRef:ProtoViewRef, injector:Injector):ViewRef {
65+
createRootHostView(hostProtoViewRef:ProtoViewRef, overrideSelector:string, injector:Injector):ViewRef {
6766
var hostProtoView = internalProtoView(hostProtoViewRef);
68-
var parentComponentHostView = null;
69-
var parentComponentBoundElementIndex = null;
70-
var parentRenderViewRef = null;
71-
if (isPresent(parentComponentLocation)) {
72-
parentComponentHostView = internalView(parentComponentLocation.parentView);
73-
parentComponentBoundElementIndex = parentComponentLocation.boundElementIndex;
74-
parentRenderViewRef = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex].render;
67+
var hostElementSelector = overrideSelector;
68+
if (isBlank(hostElementSelector)) {
69+
hostElementSelector = hostProtoView.elementBinders[0].componentDirective.metadata.selector;
7570
}
76-
var hostRenderView = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostProtoView.render);
77-
var hostView = this._utils.createView(hostProtoView, hostRenderView, this, this._renderer);
71+
var renderView = this._renderer.createRootHostView(hostProtoView.render, hostElementSelector);
72+
var hostView = this._utils.createView(hostProtoView, renderView, this, this._renderer);
7873
this._renderer.setEventDispatcher(hostView.render, hostView);
79-
this._createViewRecurse(hostView)
80-
this._utils.attachAndHydrateInPlaceHostView(parentComponentHostView, parentComponentBoundElementIndex, hostView, injector);
74+
this._createViewRecurse(hostView);
75+
76+
this._utils.hydrateRootHostView(hostView, injector);
8177
this._viewHydrateRecurse(hostView);
8278
return new ViewRef(hostView);
8379
}
8480

85-
destroyInPlaceHostView(parentComponentLocation:ElementRef, hostViewRef:ViewRef) {
81+
destroyRootHostView(hostViewRef:ViewRef) {
82+
// Note: Don't detach the hostView as we want to leave the
83+
// root element in place. Also don't put the hostView into the view pool
84+
// as it is depending on the element for which it was created.
8685
var hostView = internalView(hostViewRef);
87-
var parentView = null;
88-
if (isPresent(parentComponentLocation)) {
89-
parentView = internalView(parentComponentLocation.parentView).componentChildViews[parentComponentLocation.boundElementIndex];
90-
}
91-
this._destroyInPlaceHostView(parentView, hostView);
86+
// We do want to destroy the component view though.
87+
this._viewDehydrateRecurse(hostView, true);
88+
this._renderer.destroyView(hostView.render);
89+
}
90+
91+
createFreeHostView(parentComponentLocation:ElementRef, hostProtoViewRef:ProtoViewRef, injector:Injector):ViewRef {
92+
var hostProtoView = internalProtoView(hostProtoViewRef);
93+
var hostView = this._createPooledView(hostProtoView);
94+
var parentComponentHostView = internalView(parentComponentLocation.parentView);
95+
var parentComponentBoundElementIndex = parentComponentLocation.boundElementIndex;
96+
this._utils.attachAndHydrateFreeHostView(parentComponentHostView, parentComponentBoundElementIndex, hostView, injector);
97+
this._viewHydrateRecurse(hostView);
98+
return new ViewRef(hostView);
99+
}
100+
101+
destroyFreeHostView(parentComponentLocation:ElementRef, hostViewRef:ViewRef) {
102+
var hostView = internalView(hostViewRef);
103+
var parentView = internalView(parentComponentLocation.parentView).componentChildViews[parentComponentLocation.boundElementIndex];
104+
this._destroyFreeHostView(parentView, hostView);
92105
}
93106

94107
createViewInContainer(viewContainerLocation:ElementRef,
@@ -186,16 +199,11 @@ export class AppViewManager {
186199
this._destroyPooledView(componentView);
187200
}
188201

189-
_destroyInPlaceHostView(parentView, hostView) {
190-
var parentRenderViewRef = null;
191-
if (isPresent(parentView)) {
192-
parentRenderViewRef = parentView.render;
193-
}
202+
_destroyFreeHostView(parentView, hostView) {
194203
this._viewDehydrateRecurse(hostView, true);
195-
this._utils.detachInPlaceHostView(parentView, hostView);
196-
this._renderer.destroyInPlaceHostView(parentRenderViewRef, hostView.render);
197-
// Note: Don't put the inplace host view into the view pool
198-
// as it is depending on the element for which it was created.
204+
this._renderer.detachFreeHostView(parentView.render, hostView.render);
205+
this._utils.detachFreeHostView(parentView, hostView);
206+
this._destroyPooledView(hostView);
199207
}
200208

201209
_viewHydrateRecurse(
@@ -234,10 +242,10 @@ export class AppViewManager {
234242
}
235243
}
236244

237-
// inPlaceHostViews
238-
for (var i = view.inPlaceHostViews.length-1; i>=0; i--) {
239-
var hostView = view.inPlaceHostViews[i];
240-
this._destroyInPlaceHostView(view, hostView);
245+
// freeHostViews
246+
for (var i = view.freeHostViews.length-1; i>=0; i--) {
247+
var hostView = view.freeHostViews[i];
248+
this._destroyFreeHostView(view, hostView);
241249
}
242250
}
243251
}

Diff for: modules/angular2/src/core/compiler/view_manager_utils.js

+12-13
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,23 @@ export class AppViewManagerUtils {
9393
);
9494
}
9595

96-
attachAndHydrateInPlaceHostView(parentComponentHostView:viewModule.AppView, parentComponentBoundElementIndex:number,
96+
hydrateRootHostView(hostView:viewModule.AppView, injector:Injector = null) {
97+
this._hydrateView(hostView, injector, null, new Object(), null);
98+
}
99+
100+
attachAndHydrateFreeHostView(parentComponentHostView:viewModule.AppView, parentComponentBoundElementIndex:number,
97101
hostView:viewModule.AppView, injector:Injector = null) {
98-
var hostElementInjector = null;
99-
if (isPresent(parentComponentHostView)) {
100-
hostElementInjector = parentComponentHostView.elementInjectors[parentComponentBoundElementIndex];
101-
var parentView = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex];
102-
parentView.changeDetector.addChild(hostView.changeDetector);
103-
ListWrapper.push(parentView.inPlaceHostViews, hostView);
104-
}
102+
var hostElementInjector = parentComponentHostView.elementInjectors[parentComponentBoundElementIndex];
103+
var parentView = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex];
104+
parentView.changeDetector.addChild(hostView.changeDetector);
105+
ListWrapper.push(parentView.freeHostViews, hostView);
105106
this._hydrateView(hostView, injector, hostElementInjector, new Object(), null);
106107
}
107108

108-
detachInPlaceHostView(parentView:viewModule.AppView,
109+
detachFreeHostView(parentView:viewModule.AppView,
109110
hostView:viewModule.AppView) {
110-
if (isPresent(parentView)) {
111-
parentView.changeDetector.removeChild(hostView.changeDetector);
112-
ListWrapper.remove(parentView.inPlaceHostViews, hostView);
113-
}
111+
parentView.changeDetector.removeChild(hostView.changeDetector);
112+
ListWrapper.remove(parentView.freeHostViews, hostView);
114113
}
115114

116115
attachViewInContainer(parentView:viewModule.AppView, boundElementIndex:number,

Diff for: modules/angular2/src/render/api.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -187,20 +187,19 @@ export class RenderCompiler {
187187

188188
export class Renderer {
189189
/**
190-
* Creates a host view that includes the given element.
191-
* @param {RenderViewRef} parentHostViewRef (might be null)
192-
* @param {any} hostElementSelector css selector for the host element
190+
* Creates a root host view that includes the given element.
193191
* @param {RenderProtoViewRef} hostProtoViewRef a RenderProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE
192+
* @param {any} hostElementSelector css selector for the host element (will be queried against the main document)
194193
* @return {RenderViewRef} the created view
195194
*/
196-
createInPlaceHostView(parentHostViewRef:RenderViewRef, hostElementSelector:string, hostProtoViewRef:RenderProtoViewRef):RenderViewRef {
195+
createRootHostView(hostProtoViewRef:RenderProtoViewRef, hostElementSelector:string):RenderViewRef {
197196
return null;
198197
}
199198

200199
/**
201-
* Destroys the given host view in the given parent view.
200+
* Detaches a free host view's element from the DOM.
202201
*/
203-
destroyInPlaceHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
202+
detachFreeHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
204203
}
205204

206205
/**

Diff for: modules/angular2/src/render/dom/dom_renderer.js

+9-17
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ import {Renderer, RenderProtoViewRef, RenderViewRef} from '../api';
1919
// const expressions!
2020
export const DOCUMENT_TOKEN = 'DocumentToken';
2121

22-
var _DOCUMENT_SELECTOR_REGEX = RegExpWrapper.create('\\:document(.+)');
23-
2422
@Injectable()
2523
export class DomRenderer extends Renderer {
2624
_eventManager:EventManager;
@@ -34,27 +32,16 @@ export class DomRenderer extends Renderer {
3432
this._document = document;
3533
}
3634

37-
createInPlaceHostView(parentHostViewRef:RenderViewRef, hostElementSelector:string, hostProtoViewRef:RenderProtoViewRef):RenderViewRef {
38-
var containerNode;
39-
var documentSelectorMatch = RegExpWrapper.firstMatch(_DOCUMENT_SELECTOR_REGEX, hostElementSelector);
40-
if (isPresent(documentSelectorMatch)) {
41-
containerNode = this._document;
42-
hostElementSelector = documentSelectorMatch[1];
43-
} else if (isPresent(parentHostViewRef)) {
44-
var parentHostView = resolveInternalDomView(parentHostViewRef);
45-
containerNode = parentHostView.shadowRoot;
46-
} else {
47-
containerNode = this._document;
48-
}
49-
var element = DOM.querySelector(containerNode, hostElementSelector);
35+
createRootHostView(hostProtoViewRef:RenderProtoViewRef, hostElementSelector:string):RenderViewRef {
36+
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
37+
var element = DOM.querySelector(this._document, hostElementSelector);
5038
if (isBlank(element)) {
5139
throw new BaseException(`The selector "${hostElementSelector}" did not match any elements`);
5240
}
53-
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
5441
return new DomViewRef(this._createView(hostProtoView, element));
5542
}
5643

57-
destroyInPlaceHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
44+
detachFreeHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
5845
var hostView = resolveInternalDomView(hostViewRef);
5946
this._removeViewNodes(hostView);
6047
}
@@ -89,6 +76,11 @@ export class DomRenderer extends Renderer {
8976
this._moveViewNodesIntoParent(componentView.shadowRoot, componentView);
9077
}
9178

79+
getHostElement(hostViewRef:RenderViewRef) {
80+
var hostView = resolveInternalDomView(hostViewRef);
81+
return hostView.boundElements[0];
82+
}
83+
9284
detachComponentView(hostViewRef:RenderViewRef, boundElementIndex:number, componentViewRef:RenderViewRef) {
9385
var hostView = resolveInternalDomView(hostViewRef);
9486
var componentView = resolveInternalDomView(componentViewRef);

Diff for: modules/angular2/src/test_lib/test_bed.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class TestBed {
9494
DOM.appendChild(doc.body, rootEl);
9595

9696
var componentBinding = bind(component).toValue(context);
97-
return this._injector.get(DynamicComponentLoader).loadIntoNewLocation(componentBinding, null, '#root', this._injector).then((hostComponentRef) => {
97+
return this._injector.get(DynamicComponentLoader).loadAsRoot(componentBinding,'#root', this._injector).then((hostComponentRef) => {
9898
return new ViewProxy(hostComponentRef);
9999
});
100100
}

Diff for: modules/angular2/test/core/compiler/dynamic_component_loader_spec.js

+41-6
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@ import {
1111
inject,
1212
beforeEachBindings,
1313
it,
14-
xit
14+
xit,
15+
viewRootNodes
1516
} from 'angular2/test_lib';
1617

17-
import {TestBed} from 'angular2/src/test_lib/test_bed';
18-
18+
import {TestBed, ViewProxy} from 'angular2/src/test_lib/test_bed';
19+
import {Injector} from 'angular2/di';
1920
import {Component} from 'angular2/src/core/annotations_impl/annotations';
2021
import {View} from 'angular2/src/core/annotations_impl/view';
2122
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
2223
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
2324
import {NgIf} from 'angular2/src/directives/ng_if';
24-
import {DomRenderer} from 'angular2/src/render/dom/dom_renderer';
25+
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
2526
import {DOM} from 'angular2/src/dom/dom_adapter';
2627
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
2728

@@ -193,14 +194,44 @@ export function main() {
193194

194195
});
195196

197+
describe('loadAsRoot', () => {
198+
199+
it('should allow to create, update and destroy components',
200+
inject([TestBed, AsyncTestCompleter, DynamicComponentLoader, DOCUMENT_TOKEN, Injector], (tb, async, dcl, doc, injector) => {
201+
var rootEl = el('<child-cmp></child-cmp>');
202+
DOM.appendChild(doc.body, rootEl);
203+
dcl.loadAsRoot(ChildComp, null, injector).then( (componentRef) => {
204+
var view = new ViewProxy(componentRef);
205+
expect(rootEl.parentNode).toBe(doc.body);
206+
207+
view.detectChanges();
208+
209+
expect(rootEl).toHaveText('hello');
210+
211+
componentRef.instance.ctxProp = 'new';
212+
213+
view.detectChanges();
214+
215+
expect(rootEl).toHaveText('new');
216+
217+
componentRef.dispose();
218+
219+
expect(rootEl).toHaveText('');
220+
expect(rootEl.parentNode).toBe(doc.body);
221+
222+
async.done();
223+
});
224+
}));
225+
226+
});
227+
196228
});
197229
}
198230

199231
@Component({
200232
selector: 'imp-ng-cmp'
201233
})
202234
@View({
203-
renderer: 'imp-ng-cmp-renderer',
204235
template: ''
205236
})
206237
class ImperativeViewComponentUsingNgComponent {
@@ -210,7 +241,11 @@ class ImperativeViewComponentUsingNgComponent {
210241
var div = el('<div id="impHost"></div>');
211242
var shadowViewRef = viewManager.getComponentView(self);
212243
renderer.setComponentViewRootNodes(shadowViewRef.render, [div]);
213-
this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, '#impHost', null);
244+
this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, null).then( (componentRef) => {
245+
var element = renderer.getHostElement(componentRef.hostView.render);
246+
DOM.appendChild(div, element);
247+
return componentRef;
248+
});
214249
}
215250
}
216251

0 commit comments

Comments
 (0)