feat(view): allow to transplant a view into a ViewContainer at another place.
Closes #1492.
This commit is contained in:
parent
2185e7cee9
commit
4f3433b5bd
|
@ -88,7 +88,7 @@ export class DynamicComponentLoader {
|
|||
var binding = this._getBinding(typeOrBinding);
|
||||
return this._compiler.compileInHost(binding).then(hostProtoViewRef => {
|
||||
var viewContainer = this._viewManager.getViewContainer(location);
|
||||
var hostViewRef = viewContainer.create(hostProtoViewRef, viewContainer.length, injector);
|
||||
var hostViewRef = viewContainer.create(hostProtoViewRef, viewContainer.length, null, injector);
|
||||
var newLocation = new ElementRef(hostViewRef, 0);
|
||||
var component = this._viewManager.getComponent(newLocation);
|
||||
|
||||
|
|
|
@ -40,9 +40,9 @@ export class ViewContainerRef {
|
|||
|
||||
// TODO(rado): profile and decide whether bounds checks should be added
|
||||
// to the methods below.
|
||||
create(protoViewRef:ProtoViewRef = null, atIndex:number=-1, injector:Injector = null): ViewRef {
|
||||
create(protoViewRef:ProtoViewRef = null, atIndex:number=-1, context:ElementRef, injector:Injector = null): ViewRef {
|
||||
if (atIndex == -1) atIndex = this.length;
|
||||
return this._viewManager.createViewInContainer(this._element, atIndex, protoViewRef, injector);
|
||||
return this._viewManager.createViewInContainer(this._element, atIndex, protoViewRef, context, injector);
|
||||
}
|
||||
|
||||
insert(viewRef:ViewRef, atIndex:number=-1): ViewRef {
|
||||
|
@ -68,4 +68,4 @@ export class ViewContainerRef {
|
|||
if (atIndex == -1) atIndex = this.length - 1;
|
||||
return this._viewManager.detachViewInContainer(this._element, atIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,16 +92,22 @@ export class AppViewManager {
|
|||
}
|
||||
|
||||
createViewInContainer(viewContainerLocation:ElementRef,
|
||||
atIndex:number, protoViewRef:ProtoViewRef, injector:Injector = null):ViewRef {
|
||||
atIndex:number, protoViewRef:ProtoViewRef, context:ElementRef = null, injector:Injector = null):ViewRef {
|
||||
var protoView = internalProtoView(protoViewRef);
|
||||
var parentView = internalView(viewContainerLocation.parentView);
|
||||
var boundElementIndex = viewContainerLocation.boundElementIndex;
|
||||
var contextView = null;
|
||||
var contextBoundElementIndex = null;
|
||||
if (isPresent(context)) {
|
||||
contextView = internalView(context.parentView);
|
||||
contextBoundElementIndex = context.boundElementIndex;
|
||||
}
|
||||
|
||||
var view = this._createPooledView(protoView);
|
||||
|
||||
this._renderer.attachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render);
|
||||
this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view);
|
||||
this._utils.hydrateViewInContainer(parentView, boundElementIndex, atIndex, injector);
|
||||
this._utils.attachViewInContainer(parentView, boundElementIndex, contextView, contextBoundElementIndex, atIndex, view);
|
||||
this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView, contextBoundElementIndex, atIndex, injector);
|
||||
this._viewHydrateRecurse(view);
|
||||
return new ViewRef(view);
|
||||
}
|
||||
|
@ -116,7 +122,13 @@ export class AppViewManager {
|
|||
var view = internalView(viewRef);
|
||||
var parentView = internalView(viewContainerLocation.parentView);
|
||||
var boundElementIndex = viewContainerLocation.boundElementIndex;
|
||||
this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view);
|
||||
// TODO(tbosch): the public methods attachViewInContainer/detachViewInContainer
|
||||
// are used for moving elements without the same container.
|
||||
// We will change this into an atomic `move` operation, which should preserve the
|
||||
// previous parent injector (see https://github.com/angular/angular/issues/1377).
|
||||
// Right now we are destroying any special
|
||||
// context view that might have been used.
|
||||
this._utils.attachViewInContainer(parentView, boundElementIndex, null, null, atIndex, view);
|
||||
this._renderer.attachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render);
|
||||
return viewRef;
|
||||
}
|
||||
|
|
|
@ -114,7 +114,12 @@ export class AppViewManagerUtils {
|
|||
}
|
||||
|
||||
attachViewInContainer(parentView:viewModule.AppView, boundElementIndex:number,
|
||||
contextView:viewModule.AppView, contextBoundElementIndex:number,
|
||||
atIndex:number, view:viewModule.AppView) {
|
||||
if (isBlank(contextView)) {
|
||||
contextView = parentView;
|
||||
contextBoundElementIndex = boundElementIndex;
|
||||
}
|
||||
parentView.changeDetector.addChild(view.changeDetector);
|
||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||
if (isBlank(viewContainer)) {
|
||||
|
@ -128,7 +133,7 @@ export class AppViewManagerUtils {
|
|||
} else {
|
||||
sibling = ListWrapper.last(viewContainer.views[atIndex - 1].rootElementInjectors)
|
||||
}
|
||||
var elementInjector = parentView.elementInjectors[boundElementIndex];
|
||||
var elementInjector = contextView.elementInjectors[contextBoundElementIndex];
|
||||
for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) {
|
||||
view.rootElementInjectors[i].linkAfter(elementInjector, sibling);
|
||||
}
|
||||
|
@ -145,11 +150,16 @@ export class AppViewManagerUtils {
|
|||
}
|
||||
|
||||
hydrateViewInContainer(parentView:viewModule.AppView, boundElementIndex:number,
|
||||
contextView:viewModule.AppView, contextBoundElementIndex:number,
|
||||
atIndex:number, injector:Injector) {
|
||||
if (isBlank(contextView)) {
|
||||
contextView = parentView;
|
||||
contextBoundElementIndex = boundElementIndex;
|
||||
}
|
||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||
var view = viewContainer.views[atIndex];
|
||||
var elementInjector = parentView.elementInjectors[boundElementIndex];
|
||||
this._hydrateView(view, injector, elementInjector, parentView.context, parentView.locals);
|
||||
var elementInjector = contextView.elementInjectors[contextBoundElementIndex].getHost();
|
||||
this._hydrateView(view, injector, elementInjector, contextView.context, contextView.locals);
|
||||
}
|
||||
|
||||
hydrateDynamicComponentInElementInjector(hostView:viewModule.AppView, boundElementIndex:number,
|
||||
|
|
|
@ -37,7 +37,7 @@ export class RouterOutlet {
|
|||
]);
|
||||
|
||||
this._viewContainer.clear();
|
||||
this._viewContainer.create(pv, 0, outletInjector);
|
||||
this._viewContainer.create(pv, 0, null, outletInjector);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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)}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue