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
This commit is contained in:
Tobias Bosch
2015-05-15 09:55:43 -07:00
parent a38a0d6f87
commit 421d8916a6
14 changed files with 310 additions and 158 deletions
@@ -11,17 +11,18 @@ import {
inject,
beforeEachBindings,
it,
xit
xit,
viewRootNodes
} from 'angular2/test_lib';
import {TestBed} from 'angular2/src/test_lib/test_bed';
import {TestBed, ViewProxy} from 'angular2/src/test_lib/test_bed';
import {Injector} from 'angular2/di';
import {Component} from 'angular2/src/core/annotations_impl/annotations';
import {View} from 'angular2/src/core/annotations_impl/view';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
import {NgIf} from 'angular2/src/directives/ng_if';
import {DomRenderer} from 'angular2/src/render/dom/dom_renderer';
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
@@ -193,6 +194,37 @@ export function main() {
});
describe('loadAsRoot', () => {
it('should allow to create, update and destroy components',
inject([TestBed, AsyncTestCompleter, DynamicComponentLoader, DOCUMENT_TOKEN, Injector], (tb, async, dcl, doc, injector) => {
var rootEl = el('<child-cmp></child-cmp>');
DOM.appendChild(doc.body, rootEl);
dcl.loadAsRoot(ChildComp, null, injector).then( (componentRef) => {
var view = new ViewProxy(componentRef);
expect(rootEl.parentNode).toBe(doc.body);
view.detectChanges();
expect(rootEl).toHaveText('hello');
componentRef.instance.ctxProp = 'new';
view.detectChanges();
expect(rootEl).toHaveText('new');
componentRef.dispose();
expect(rootEl).toHaveText('');
expect(rootEl.parentNode).toBe(doc.body);
async.done();
});
}));
});
});
}
@@ -200,7 +232,6 @@ export function main() {
selector: 'imp-ng-cmp'
})
@View({
renderer: 'imp-ng-cmp-renderer',
template: ''
})
class ImperativeViewComponentUsingNgComponent {
@@ -210,7 +241,11 @@ class ImperativeViewComponentUsingNgComponent {
var div = el('<div id="impHost"></div>');
var shadowViewRef = viewManager.getComponentView(self);
renderer.setComponentViewRootNodes(shadowViewRef.render, [div]);
this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, '#impHost', null);
this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, null).then( (componentRef) => {
var element = renderer.getHostElement(componentRef.hostView.render);
DOM.appendChild(div, element);
return componentRef;
});
}
}
+94 -23
View File
@@ -141,7 +141,7 @@ export function main() {
}
ListWrapper.insert(viewContainer.views, atIndex, childView);
});
renderer.spy('createInPlaceHostView').andCallFake( (_a, _b, _c) => {
renderer.spy('createRootHostView').andCallFake( (_b, _c) => {
var rv = new RenderViewRef();
ListWrapper.push(createdRenderViews, rv);
return rv;
@@ -294,7 +294,7 @@ export function main() {
});
describe('createInPlaceHostView', () => {
describe('createFreeHostView', () => {
// Note: We don't add tests for recursion or viewpool here as we assume that
// this is using the same mechanism as the other methods...
@@ -314,27 +314,26 @@ export function main() {
it('should create the view', () => {
expect(
internalView(manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), null))
internalView(manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null))
).toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(hostProtoView);
});
it('should attachAndHydrate the view', () => {
var injector = new Injector([], null, false);
manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), injector);
expect(utils.spy('attachAndHydrateInPlaceHostView')).toHaveBeenCalledWith(parentHostView, 0, createdViews[0], injector);
manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), injector);
expect(utils.spy('attachAndHydrateFreeHostView')).toHaveBeenCalledWith(parentHostView, 0, createdViews[0], injector);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
});
it('should create and set the render view', () => {
var elementOrSelector = 'someSelector';
manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), elementOrSelector, wrapPv(hostProtoView), null)
expect(renderer.spy('createInPlaceHostView')).toHaveBeenCalledWith(parentView.render, elementOrSelector, hostProtoView.render);
manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null)
expect(renderer.spy('createView')).toHaveBeenCalledWith(hostProtoView.render);
expect(createdViews[0].render).toBe(createdRenderViews[0]);
});
it('should set the event dispatcher', () => {
manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), null);
manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null);
var cmpView = createdViews[0];
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView);
});
@@ -343,7 +342,7 @@ export function main() {
});
describe('destroyInPlaceHostView', () => {
describe('destroyFreeHostView', () => {
describe('basic functionality', () => {
var parentHostView, parentView, hostProtoView, hostView, hostRenderViewRef;
beforeEach( () => {
@@ -355,29 +354,29 @@ export function main() {
hostProtoView = createProtoView(
[createComponentElBinder(null)]
);
hostView = internalView(manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), null));
hostView = internalView(manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null));
hostRenderViewRef = hostView.render;
});
it('should detach', () => {
manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(utils.spy('detachInPlaceHostView')).toHaveBeenCalledWith(parentView, hostView);
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(utils.spy('detachFreeHostView')).toHaveBeenCalledWith(parentView, hostView);
});
it('should dehydrate', () => {
manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
});
it('should destroy and clear the render view', () => {
manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(renderer.spy('destroyInPlaceHostView')).toHaveBeenCalledWith(parentView.render, hostRenderViewRef);
it('should detach the render view', () => {
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(renderer.spy('detachFreeHostView')).toHaveBeenCalledWith(parentView.render, hostRenderViewRef);
});
it('should not return the view to the pool', () => {
manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(viewPool.spy('returnView')).not.toHaveBeenCalled();
it('should return the view to the pool', () => {
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(hostView);
});
});
@@ -387,6 +386,78 @@ export function main() {
});
describe('createRootHostView', () => {
var hostProtoView;
beforeEach( () => {
hostProtoView = createProtoView(
[createComponentElBinder(null)]
);
});
it('should create the view', () => {
expect(
internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null))
).toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(hostProtoView);
});
it('should hydrate the view', () => {
var injector = new Injector([], null, false);
manager.createRootHostView(wrapPv(hostProtoView), null, injector);
expect(utils.spy('hydrateRootHostView')).toHaveBeenCalledWith(createdViews[0], injector);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
});
it('should create and set the render view using the component selector', () => {
manager.createRootHostView(wrapPv(hostProtoView), null, null)
expect(renderer.spy('createRootHostView')).toHaveBeenCalledWith(hostProtoView.render, 'someComponent');
expect(createdViews[0].render).toBe(createdRenderViews[0]);
});
it('should allow to override the selector', () => {
var selector = 'someOtherSelector';
manager.createRootHostView(wrapPv(hostProtoView), selector, null)
expect(renderer.spy('createRootHostView')).toHaveBeenCalledWith(hostProtoView.render, selector);
});
it('should set the event dispatcher', () => {
manager.createRootHostView(wrapPv(hostProtoView), null, null);
var cmpView = createdViews[0];
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView);
});
});
describe('destroyRootHostView', () => {
var hostProtoView, hostView, hostRenderViewRef;
beforeEach( () => {
hostProtoView = createProtoView(
[createComponentElBinder(null)]
);
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
hostRenderViewRef = hostView.render;
});
it('should dehydrate', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
});
it('should destroy the render view', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(renderer.spy('destroyView')).toHaveBeenCalledWith(hostRenderViewRef);
});
it('should not return the view to the pool', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(viewPool.spy('returnView')).not.toHaveBeenCalled();
});
});
describe('createViewInContainer', () => {
describe('basic functionality', () => {
@@ -483,19 +554,19 @@ export function main() {
});
it('should dehydrate', () => {
manager.destroyInPlaceHostView(null, wrapView(parentView));
manager.destroyRootHostView(wrapView(parentView));
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(parentView.viewContainers[0].views[0]);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
});
it('should detach', () => {
manager.destroyInPlaceHostView(null, wrapView(parentView));
manager.destroyRootHostView(wrapView(parentView));
expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0);
expect(renderer.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView.render, 0, 0, childView.render);
});
it('should return the view to the pool', () => {
manager.destroyInPlaceHostView(null, wrapView(parentView));
manager.destroyRootHostView(wrapView(parentView));
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView);
});
@@ -31,7 +31,6 @@ import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils
export function main() {
// TODO(tbosch): add more tests here!
describe('AppViewManagerUtils', () => {
var directiveResolver;
@@ -170,7 +169,7 @@ export function main() {
var shadowView = createView();
utils.attachComponentView(hostView, 0, shadowView);
utils.attachAndHydrateInPlaceHostView(null, null, hostView, createInjector());
utils.hydrateRootHostView(hostView, createInjector());
expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
expect(spyEventAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
@@ -200,7 +199,7 @@ export function main() {
var shadowView = createView();
utils.attachComponentView(hostView, 0, shadowView);
utils.attachAndHydrateInPlaceHostView(null, null, hostView, createInjector());
utils.hydrateRootHostView(hostView, createInjector());
expect(spyActionAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
expect(spyActionAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
@@ -268,6 +267,27 @@ export function main() {
});
describe('hydrateRootHostView', () => {
var hostView;
function createViews() {
var hostPv = createProtoView([
createComponentElBinder()
]);
hostView = createView(hostPv);
}
it("should instantiate the elementInjectors with the given injector and an empty host element injector", () => {
var injector = createInjector();
createViews();
utils.hydrateRootHostView(hostView, injector);
expect(hostView.rootElementInjectors[0].spy('instantiateDirectives'))
.toHaveBeenCalledWith(injector, null, hostView.preBuiltObjects[0]);
});
});
});
}