feat(view): allow to transplant a view into a ViewContainer at another place.

Closes #1492.
This commit is contained in:
Tobias Bosch
2015-05-08 17:58:08 -07:00
parent 2185e7cee9
commit 4f3433b5bd
9 changed files with 199 additions and 34 deletions
+66 -2
View File
@@ -27,11 +27,13 @@ import {PipeRegistry, defaultPipeRegistry,
import {Directive, Component} from 'angular2/src/core/annotations_impl/annotations';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {QueryList} from 'angular2/src/core/compiler/query_list';
import {View} from 'angular2/src/core/annotations_impl/view';
import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility';
import {Attribute} from 'angular2/src/core/annotations_impl/di';
import {Attribute, Query} from 'angular2/src/core/annotations_impl/di';
import {If} from 'angular2/src/directives/if';
import {For} from 'angular2/src/directives/for';
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref';
@@ -341,6 +343,21 @@ export function main() {
});
}));
it('should allow to transplant embedded ProtoViews into other ViewContainers', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({
template: '<some-directive><toolbar><template toolbarpart var-toolbar-prop="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-parent></cmp-with-parent></template></toolbar></some-directive>',
directives: [SomeDirective, CompWithParent, ToolbarComponent, ToolbarPart]
}));
ctx.ctxProp = 'From myComp';
tb.createView(MyComp, {context: ctx}).then((view) => {
view.detectChanges();
expect(view.rootNodes).toHaveText('TOOLBAR(From myComp,From toolbar,Component with an injected parent)');
async.done();
});
}));
it('should assign the component instance to a var-', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({
template: '<p><child-cmp var-alice></child-cmp></p>',
@@ -951,7 +968,7 @@ class DynamicViewport {
var myService = new MyService();
myService.greeting = 'dynamic greet';
this.done = compiler.compileInHost(ChildCompUsingService).then( (hostPv) => {
vc.create(hostPv, 0, inj.createChildFromResolved(Injector.resolve([bind(MyService).toValue(myService)])))
vc.create(hostPv, 0, null, inj.createChildFromResolved(Injector.resolve([bind(MyService).toValue(myService)])))
});
}
}
@@ -1411,3 +1428,50 @@ class ChildComponent {
this.appDependency = a;
}
}
@Directive({
selector: '[toolbar-vc]',
properties: {
'toolbarVc': 'toolbarVc'
}
})
class ToolbarViewContainer {
vc:ViewContainerRef;
constructor(vc:ViewContainerRef) {
this.vc = vc;
}
set toolbarVc(part:ToolbarPart) {
var view = this.vc.create(part.protoViewRef, 0, part.elementRef);
view.setLocal('toolbarProp', 'From toolbar');
}
}
@Directive({
selector: '[toolbarpart]'
})
class ToolbarPart {
protoViewRef:ProtoViewRef;
elementRef:ElementRef;
constructor(protoViewRef:ProtoViewRef, elementRef:ElementRef) {
this.elementRef = elementRef;
this.protoViewRef = protoViewRef;
}
}
@Component({
selector: 'toolbar'
})
@View({
template: 'TOOLBAR(<div *for="var part of query" [toolbar-vc]="part"></div>)',
directives: [ToolbarViewContainer, For]
})
class ToolbarComponent {
query:QueryList;
ctxProp:string;
constructor(@Query(ToolbarPart) query: QueryList) {
this.ctxProp = 'hello world';
this.query = query;
}
}
@@ -55,16 +55,20 @@ export function main() {
location = new ElementRef(wrapView(view), 0);
});
it('should return a 0 length if there is no underlying ViewContainerRef', () => {
var vc = createViewContainer();
expect(vc.length).toBe(0);
});
describe('length', () => {
it('should return a 0 length if there is no underlying ViewContainerRef', () => {
var vc = createViewContainer();
expect(vc.length).toBe(0);
});
it('should return the size of the underlying ViewContainerRef', () => {
var vc = createViewContainer();
view.viewContainers = [new AppViewContainer()];
view.viewContainers[0].views = [createView()];
expect(vc.length).toBe(1);
});
it('should return the size of the underlying ViewContainerRef', () => {
var vc = createViewContainer();
view.viewContainers = [new AppViewContainer()];
view.viewContainers[0].views = [createView()];
expect(vc.length).toBe(1);
});
// TODO: add missing tests here!
+11 -7
View File
@@ -133,7 +133,7 @@ export function main() {
utils.spy('attachComponentView').andCallFake( (hostView, elementIndex, childView) => {
hostView.componentChildViews[elementIndex] = childView;
});
utils.spy('attachViewInContainer').andCallFake( (parentView, elementIndex, atIndex, childView) => {
utils.spy('attachViewInContainer').andCallFake( (parentView, elementIndex, _a, _b, atIndex, childView) => {
var viewContainer = parentView.viewContainers[elementIndex];
if (isBlank(viewContainer)) {
viewContainer = new AppViewContainer();
@@ -411,26 +411,30 @@ export function main() {
});
it('should attach the view', () => {
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null)
expect(utils.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, createdViews[0]);
var contextView = createView();
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView),
elementRef(wrapView(contextView), 1), null);
expect(utils.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, createdViews[0]);
expect(renderer.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView.render, 0, 0, createdViews[0].render);
});
it('should hydrate the view', () => {
var injector = new Injector([], null, false);
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), injector);
expect(utils.spy('hydrateViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, injector);
var contextView = createView();
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView),
elementRef(wrapView(contextView), 1), injector);
expect(utils.spy('hydrateViewInContainer')).toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, injector);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
});
it('should create and set the render view', () => {
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null);
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null, null);
expect(renderer.spy('createView')).toHaveBeenCalledWith(childProtoView.render);
expect(createdViews[0].render).toBe(createdRenderViews[0]);
});
it('should set the event dispatcher', () => {
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null);
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null, null);
var childView = createdViews[0];
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(childView.render, childView);
});
@@ -23,7 +23,7 @@ import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/col
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {ChangeDetector} from 'angular2/change_detection';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveBinding, ElementInjector, ElementRef} from 'angular2/src/core/compiler/element_injector';
import {DirectiveBinding, ElementInjector, PreBuiltObjects} from 'angular2/src/core/compiler/element_injector';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {Component} from 'angular2/src/core/annotations_impl/annotations';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
@@ -66,12 +66,14 @@ export function main() {
}
function createElementInjector() {
var host = new SpyElementInjector();
return SpyObject.stub(new SpyElementInjector(), {
'isExportingComponent' : false,
'isExportingElement' : false,
'getEventEmitterAccessors' : [],
'getComponent' : null,
'getDynamicallyLoadedComponent': null
'getDynamicallyLoadedComponent': null,
'getHost': host
}, {});
}
@@ -81,13 +83,15 @@ export function main() {
}
var view = new AppView(null, pv, MapWrapper.create());
var elementInjectors = ListWrapper.createFixedSize(pv.elementBinders.length);
var preBuiltObjects = ListWrapper.createFixedSize(pv.elementBinders.length);
for (var i=0; i<pv.elementBinders.length; i++) {
elementInjectors[i] = createElementInjector();
preBuiltObjects[i] = new SpyPreBuiltObjects();
}
view.init(new SpyChangeDetector(),
elementInjectors,
[],
ListWrapper.createFixedSize(pv.elementBinders.length),
elementInjectors,
preBuiltObjects,
ListWrapper.createFixedSize(pv.elementBinders.length)
);
return view;
@@ -170,6 +174,66 @@ export function main() {
});
describe('attachViewInContainer', () => {
var parentView, contextView, childView;
function createViews() {
var parentPv = createProtoView([
createEmptyElBinder()
]);
parentView = createView(parentPv);
var contextPv = createProtoView([
createEmptyElBinder()
]);
contextView = createView(contextPv);
var childPv = createProtoView([
createEmptyElBinder()
]);
childView = createView(childPv);
}
it('should link the views rootElementInjectors after the elementInjector at the given context', () => {
createViews();
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
expect(childView.rootElementInjectors[0].spy('linkAfter'))
.toHaveBeenCalledWith(contextView.elementInjectors[0], null);
});
});
describe('hydrateViewInContainer', () => {
var parentView, contextView, childView;
function createViews() {
var parentPv = createProtoView([
createEmptyElBinder()
]);
parentView = createView(parentPv);
var contextPv = createProtoView([
createEmptyElBinder()
]);
contextView = createView(contextPv);
var childPv = createProtoView([
createEmptyElBinder()
]);
childView = createView(childPv);
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
}
it("should instantiate the elementInjectors with the host of the context's elementInjector", () => {
createViews();
utils.hydrateViewInContainer(parentView, 0, contextView, 0, 0, null);
expect(childView.rootElementInjectors[0].spy('instantiateDirectives'))
.toHaveBeenCalledWith(null, contextView.elementInjectors[0].getHost(), childView.preBuiltObjects[0]);
});
});
});
}
@@ -190,3 +254,10 @@ class SpyChangeDetector extends SpyObject {
constructor(){super(ChangeDetector);}
noSuchMethod(m){return super.noSuchMethod(m)}
}
@proxy
@IMPLEMENTS(PreBuiltObjects)
class SpyPreBuiltObjects extends SpyObject {
constructor(){super(PreBuiltObjects);}
noSuchMethod(m){return super.noSuchMethod(m)}
}