feat(view): allow to transplant a view into a ViewContainer at another place.
Closes #1492.
This commit is contained in:
+66
-2
@@ -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
@@ -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)}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user