feat(view): introduce free embedded views
Free embedded views are view instances that are created logically in the same was as views of a ViewContainer, but their dom nodes are not attached. BREAKING CHANGE: - `Renderer.detachFreeHostView` was renamed to `Renderer.detachFreeView` - `DomRenderer.getHostElement()` was generalized into `DomRenderer.getRootNodes()`
This commit is contained in:
@ -257,7 +257,7 @@ class ImperativeViewComponentUsingNgComponent {
|
||||
renderer.setComponentViewRootNodes(shadowViewRef.render, [div]);
|
||||
this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, null)
|
||||
.then((componentRef) => {
|
||||
var element = renderer.getHostElement(componentRef.hostView.render);
|
||||
var element = renderer.getRootNodes(componentRef.hostView.render)[0];
|
||||
DOM.appendChild(div, element);
|
||||
return componentRef;
|
||||
});
|
||||
|
@ -29,11 +29,12 @@ import {
|
||||
isJsObject,
|
||||
global,
|
||||
stringify,
|
||||
CONST
|
||||
CONST,
|
||||
CONST_EXPR
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
import {Injector, bind, Injectable, Binding, FORWARD_REF} from 'angular2/di';
|
||||
import {Injector, bind, Injectable, Binding, FORWARD_REF, OpaqueToken, Inject} from 'angular2/di';
|
||||
import {
|
||||
PipeRegistry,
|
||||
defaultPipeRegistry,
|
||||
@ -63,17 +64,21 @@ import {NgIf} from 'angular2/src/directives/ng_if';
|
||||
import {NgFor} from 'angular2/src/directives/ng_for';
|
||||
|
||||
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
|
||||
import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref';
|
||||
import {ProtoViewRef, ViewRef} from 'angular2/src/core/compiler/view_ref';
|
||||
import {Compiler} from 'angular2/src/core/compiler/compiler';
|
||||
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
||||
|
||||
import {DomRenderer} from 'angular2/src/render/dom/dom_renderer';
|
||||
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
||||
|
||||
const ANCHOR_ELEMENT = CONST_EXPR(new OpaqueToken('AnchorElement'));
|
||||
|
||||
export function main() {
|
||||
describe('integration tests', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEachBindings(() => [bind(ANCHOR_ELEMENT).toValue(el('<div></div>'))]);
|
||||
|
||||
beforeEach(() => { ctx = new MyComp(); });
|
||||
|
||||
|
||||
@ -1124,6 +1129,28 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support free embedded views',
|
||||
inject([TestBed, AsyncTestCompleter, ANCHOR_ELEMENT], (tb, async, anchorElement) => {
|
||||
tb.overrideView(MyComp, new viewAnn.View({
|
||||
template: '<div><div *some-impvp="ctxBoolProp">hello</div></div>',
|
||||
directives: [SomeImperativeViewport]
|
||||
}));
|
||||
tb.createView(MyComp).then((view) => {
|
||||
view.detectChanges();
|
||||
expect(anchorElement).toHaveText('');
|
||||
|
||||
view.context.ctxBoolProp = true;
|
||||
view.detectChanges();
|
||||
expect(anchorElement).toHaveText('hello');
|
||||
|
||||
view.context.ctxBoolProp = false;
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// Disabled until a solution is found, refs:
|
||||
// - https://github.com/angular/angular/issues/776
|
||||
// - https://github.com/angular/angular/commit/81f3f32
|
||||
@ -1640,3 +1667,30 @@ class ChildConsumingEventBus {
|
||||
|
||||
constructor(@Unbounded() bus: EventBus) { this.bus = bus; }
|
||||
}
|
||||
|
||||
@Directive({selector: '[some-impvp]', properties: ['someImpvp']})
|
||||
@Injectable()
|
||||
class SomeImperativeViewport {
|
||||
view: ViewRef;
|
||||
anchor;
|
||||
constructor(public element: ElementRef, public protoView: ProtoViewRef,
|
||||
public viewManager: AppViewManager, public renderer: DomRenderer,
|
||||
@Inject(ANCHOR_ELEMENT) anchor) {
|
||||
this.view = null;
|
||||
this.anchor = anchor;
|
||||
}
|
||||
|
||||
set someImpvp(value: boolean) {
|
||||
if (isPresent(this.view)) {
|
||||
this.viewManager.destroyFreeEmbeddedView(this.element, this.view);
|
||||
this.view = null;
|
||||
}
|
||||
if (value) {
|
||||
this.view = this.viewManager.createFreeEmbeddedView(this.element, this.protoView);
|
||||
var nodes = this.renderer.getRootNodes(this.view.render);
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
DOM.appendChild(this.anchor, nodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -377,8 +377,7 @@ export function main() {
|
||||
|
||||
it('should detach the render view', () => {
|
||||
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
expect(renderer.spy('detachFreeHostView'))
|
||||
.toHaveBeenCalledWith(parentView.render, hostRenderViewRef);
|
||||
expect(renderer.spy('detachFreeView')).toHaveBeenCalledWith(hostRenderViewRef);
|
||||
});
|
||||
|
||||
it('should return the view to the pool', () => {
|
||||
@ -402,6 +401,101 @@ export function main() {
|
||||
|
||||
});
|
||||
|
||||
describe('createFreeEmbeddedView', () => {
|
||||
|
||||
// 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...
|
||||
|
||||
describe('basic functionality', () => {
|
||||
var parentView, childProtoView;
|
||||
beforeEach(() => {
|
||||
parentView = createView(createProtoView([createEmptyElBinder()]));
|
||||
childProtoView = createProtoView();
|
||||
});
|
||||
|
||||
it('should create the view', () => {
|
||||
expect(internalView(manager.createFreeEmbeddedView(elementRef(wrapView(parentView), 0),
|
||||
wrapPv(childProtoView), null)))
|
||||
.toBe(createdViews[0]);
|
||||
expect(createdViews[0].proto).toBe(childProtoView);
|
||||
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
|
||||
});
|
||||
|
||||
it('should attachAndHydrate the view', () => {
|
||||
var injector = new Injector([], null, false);
|
||||
manager.createFreeEmbeddedView(elementRef(wrapView(parentView), 0),
|
||||
wrapPv(childProtoView), injector);
|
||||
expect(utils.spy('attachAndHydrateFreeEmbeddedView'))
|
||||
.toHaveBeenCalledWith(parentView, 0, createdViews[0], injector);
|
||||
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
|
||||
});
|
||||
|
||||
it('should create and set the render view', () => {
|
||||
manager.createFreeEmbeddedView(elementRef(wrapView(parentView), 0),
|
||||
wrapPv(childProtoView), null);
|
||||
expect(renderer.spy('createView')).toHaveBeenCalledWith(childProtoView.render);
|
||||
expect(createdViews[0].render).toBe(createdRenderViews[0]);
|
||||
});
|
||||
|
||||
it('should set the event dispatcher', () => {
|
||||
manager.createFreeEmbeddedView(elementRef(wrapView(parentView), 0),
|
||||
wrapPv(childProtoView), null);
|
||||
var cmpView = createdViews[0];
|
||||
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('destroyFreeEmbeddedView', () => {
|
||||
describe('basic functionality', () => {
|
||||
var parentView, childProtoView, childView;
|
||||
beforeEach(() => {
|
||||
parentView = createView(createProtoView([createEmptyElBinder()]));
|
||||
childProtoView = createProtoView();
|
||||
childView = internalView(manager.createFreeEmbeddedView(
|
||||
elementRef(wrapView(parentView), 0), wrapPv(childProtoView), null));
|
||||
});
|
||||
|
||||
it('should detach', () => {
|
||||
manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView));
|
||||
expect(utils.spy('detachFreeEmbeddedView'))
|
||||
.toHaveBeenCalledWith(parentView, 0, childView);
|
||||
});
|
||||
|
||||
it('should dehydrate', () => {
|
||||
manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView));
|
||||
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(childView);
|
||||
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
|
||||
});
|
||||
|
||||
it('should detach the render view', () => {
|
||||
manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView));
|
||||
expect(renderer.spy('detachFreeView')).toHaveBeenCalledWith(childView.render);
|
||||
});
|
||||
|
||||
it('should return the view to the pool', () => {
|
||||
manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView));
|
||||
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView);
|
||||
expect(renderer.spy('destroyView')).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should destroy the view if the pool is full', () => {
|
||||
viewPool.spy('returnView').andReturn(false);
|
||||
manager.destroyFreeEmbeddedView(elementRef(wrapView(parentView), 0), wrapView(childView));
|
||||
expect(renderer.spy('destroyView')).toHaveBeenCalledWith(childView.render);
|
||||
expect(viewListener.spy('viewDestroyed')).toHaveBeenCalledWith(childView);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('recursively destroyFreeEmbeddedView', () => {
|
||||
// TODO
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('createRootHostView', () => {
|
||||
|
||||
var hostProtoView;
|
||||
|
Reference in New Issue
Block a user