X Tutup
Skip to content

Commit 4f3433b

Browse files
committed
feat(view): allow to transplant a view into a ViewContainer at another place.
Closes angular#1492.
1 parent 2185e7c commit 4f3433b

File tree

9 files changed

+199
-34
lines changed

9 files changed

+199
-34
lines changed

modules/angular2/src/core/compiler/dynamic_component_loader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class DynamicComponentLoader {
8888
var binding = this._getBinding(typeOrBinding);
8989
return this._compiler.compileInHost(binding).then(hostProtoViewRef => {
9090
var viewContainer = this._viewManager.getViewContainer(location);
91-
var hostViewRef = viewContainer.create(hostProtoViewRef, viewContainer.length, injector);
91+
var hostViewRef = viewContainer.create(hostProtoViewRef, viewContainer.length, null, injector);
9292
var newLocation = new ElementRef(hostViewRef, 0);
9393
var component = this._viewManager.getComponent(newLocation);
9494

modules/angular2/src/core/compiler/view_container_ref.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ export class ViewContainerRef {
4040

4141
// TODO(rado): profile and decide whether bounds checks should be added
4242
// to the methods below.
43-
create(protoViewRef:ProtoViewRef = null, atIndex:number=-1, injector:Injector = null): ViewRef {
43+
create(protoViewRef:ProtoViewRef = null, atIndex:number=-1, context:ElementRef, injector:Injector = null): ViewRef {
4444
if (atIndex == -1) atIndex = this.length;
45-
return this._viewManager.createViewInContainer(this._element, atIndex, protoViewRef, injector);
45+
return this._viewManager.createViewInContainer(this._element, atIndex, protoViewRef, context, injector);
4646
}
4747

4848
insert(viewRef:ViewRef, atIndex:number=-1): ViewRef {
@@ -68,4 +68,4 @@ export class ViewContainerRef {
6868
if (atIndex == -1) atIndex = this.length - 1;
6969
return this._viewManager.detachViewInContainer(this._element, atIndex);
7070
}
71-
}
71+
}

modules/angular2/src/core/compiler/view_manager.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,22 @@ export class AppViewManager {
9292
}
9393

9494
createViewInContainer(viewContainerLocation:ElementRef,
95-
atIndex:number, protoViewRef:ProtoViewRef, injector:Injector = null):ViewRef {
95+
atIndex:number, protoViewRef:ProtoViewRef, context:ElementRef = null, injector:Injector = null):ViewRef {
9696
var protoView = internalProtoView(protoViewRef);
9797
var parentView = internalView(viewContainerLocation.parentView);
9898
var boundElementIndex = viewContainerLocation.boundElementIndex;
99+
var contextView = null;
100+
var contextBoundElementIndex = null;
101+
if (isPresent(context)) {
102+
contextView = internalView(context.parentView);
103+
contextBoundElementIndex = context.boundElementIndex;
104+
}
99105

100106
var view = this._createPooledView(protoView);
101107

102108
this._renderer.attachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render);
103-
this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view);
104-
this._utils.hydrateViewInContainer(parentView, boundElementIndex, atIndex, injector);
109+
this._utils.attachViewInContainer(parentView, boundElementIndex, contextView, contextBoundElementIndex, atIndex, view);
110+
this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView, contextBoundElementIndex, atIndex, injector);
105111
this._viewHydrateRecurse(view);
106112
return new ViewRef(view);
107113
}
@@ -116,7 +122,13 @@ export class AppViewManager {
116122
var view = internalView(viewRef);
117123
var parentView = internalView(viewContainerLocation.parentView);
118124
var boundElementIndex = viewContainerLocation.boundElementIndex;
119-
this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view);
125+
// TODO(tbosch): the public methods attachViewInContainer/detachViewInContainer
126+
// are used for moving elements without the same container.
127+
// We will change this into an atomic `move` operation, which should preserve the
128+
// previous parent injector (see https://github.com/angular/angular/issues/1377).
129+
// Right now we are destroying any special
130+
// context view that might have been used.
131+
this._utils.attachViewInContainer(parentView, boundElementIndex, null, null, atIndex, view);
120132
this._renderer.attachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render);
121133
return viewRef;
122134
}

modules/angular2/src/core/compiler/view_manager_utils.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,12 @@ export class AppViewManagerUtils {
114114
}
115115

116116
attachViewInContainer(parentView:viewModule.AppView, boundElementIndex:number,
117+
contextView:viewModule.AppView, contextBoundElementIndex:number,
117118
atIndex:number, view:viewModule.AppView) {
119+
if (isBlank(contextView)) {
120+
contextView = parentView;
121+
contextBoundElementIndex = boundElementIndex;
122+
}
118123
parentView.changeDetector.addChild(view.changeDetector);
119124
var viewContainer = parentView.viewContainers[boundElementIndex];
120125
if (isBlank(viewContainer)) {
@@ -128,7 +133,7 @@ export class AppViewManagerUtils {
128133
} else {
129134
sibling = ListWrapper.last(viewContainer.views[atIndex - 1].rootElementInjectors)
130135
}
131-
var elementInjector = parentView.elementInjectors[boundElementIndex];
136+
var elementInjector = contextView.elementInjectors[contextBoundElementIndex];
132137
for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) {
133138
view.rootElementInjectors[i].linkAfter(elementInjector, sibling);
134139
}
@@ -145,11 +150,16 @@ export class AppViewManagerUtils {
145150
}
146151

147152
hydrateViewInContainer(parentView:viewModule.AppView, boundElementIndex:number,
153+
contextView:viewModule.AppView, contextBoundElementIndex:number,
148154
atIndex:number, injector:Injector) {
155+
if (isBlank(contextView)) {
156+
contextView = parentView;
157+
contextBoundElementIndex = boundElementIndex;
158+
}
149159
var viewContainer = parentView.viewContainers[boundElementIndex];
150160
var view = viewContainer.views[atIndex];
151-
var elementInjector = parentView.elementInjectors[boundElementIndex];
152-
this._hydrateView(view, injector, elementInjector, parentView.context, parentView.locals);
161+
var elementInjector = contextView.elementInjectors[contextBoundElementIndex].getHost();
162+
this._hydrateView(view, injector, elementInjector, contextView.context, contextView.locals);
153163
}
154164

155165
hydrateDynamicComponentInElementInjector(hostView:viewModule.AppView, boundElementIndex:number,

modules/angular2/src/router/router_outlet.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class RouterOutlet {
3737
]);
3838

3939
this._viewContainer.clear();
40-
this._viewContainer.create(pv, 0, outletInjector);
40+
this._viewContainer.create(pv, 0, null, outletInjector);
4141
});
4242
}
4343

modules/angular2/test/core/compiler/integration_spec.js

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ import {PipeRegistry, defaultPipeRegistry,
2727

2828
import {Directive, Component} from 'angular2/src/core/annotations_impl/annotations';
2929
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
30+
import {QueryList} from 'angular2/src/core/compiler/query_list';
3031
import {View} from 'angular2/src/core/annotations_impl/view';
3132
import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility';
32-
import {Attribute} from 'angular2/src/core/annotations_impl/di';
33+
import {Attribute, Query} from 'angular2/src/core/annotations_impl/di';
3334

3435
import {If} from 'angular2/src/directives/if';
36+
import {For} from 'angular2/src/directives/for';
3537

3638
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
3739
import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref';
@@ -341,6 +343,21 @@ export function main() {
341343
});
342344
}));
343345

346+
it('should allow to transplant embedded ProtoViews into other ViewContainers', inject([TestBed, AsyncTestCompleter], (tb, async) => {
347+
tb.overrideView(MyComp, new View({
348+
template: '<some-directive><toolbar><template toolbarpart var-toolbar-prop="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-parent></cmp-with-parent></template></toolbar></some-directive>',
349+
directives: [SomeDirective, CompWithParent, ToolbarComponent, ToolbarPart]
350+
}));
351+
352+
ctx.ctxProp = 'From myComp';
353+
tb.createView(MyComp, {context: ctx}).then((view) => {
354+
view.detectChanges();
355+
expect(view.rootNodes).toHaveText('TOOLBAR(From myComp,From toolbar,Component with an injected parent)');
356+
357+
async.done();
358+
});
359+
}));
360+
344361
it('should assign the component instance to a var-', inject([TestBed, AsyncTestCompleter], (tb, async) => {
345362
tb.overrideView(MyComp, new View({
346363
template: '<p><child-cmp var-alice></child-cmp></p>',
@@ -951,7 +968,7 @@ class DynamicViewport {
951968
var myService = new MyService();
952969
myService.greeting = 'dynamic greet';
953970
this.done = compiler.compileInHost(ChildCompUsingService).then( (hostPv) => {
954-
vc.create(hostPv, 0, inj.createChildFromResolved(Injector.resolve([bind(MyService).toValue(myService)])))
971+
vc.create(hostPv, 0, null, inj.createChildFromResolved(Injector.resolve([bind(MyService).toValue(myService)])))
955972
});
956973
}
957974
}
@@ -1411,3 +1428,50 @@ class ChildComponent {
14111428
this.appDependency = a;
14121429
}
14131430
}
1431+
1432+
@Directive({
1433+
selector: '[toolbar-vc]',
1434+
properties: {
1435+
'toolbarVc': 'toolbarVc'
1436+
}
1437+
})
1438+
class ToolbarViewContainer {
1439+
vc:ViewContainerRef;
1440+
constructor(vc:ViewContainerRef) {
1441+
this.vc = vc;
1442+
}
1443+
1444+
set toolbarVc(part:ToolbarPart) {
1445+
var view = this.vc.create(part.protoViewRef, 0, part.elementRef);
1446+
view.setLocal('toolbarProp', 'From toolbar');
1447+
}
1448+
}
1449+
1450+
@Directive({
1451+
selector: '[toolbarpart]'
1452+
})
1453+
class ToolbarPart {
1454+
protoViewRef:ProtoViewRef;
1455+
elementRef:ElementRef;
1456+
constructor(protoViewRef:ProtoViewRef, elementRef:ElementRef) {
1457+
this.elementRef = elementRef;
1458+
this.protoViewRef = protoViewRef;
1459+
}
1460+
}
1461+
1462+
@Component({
1463+
selector: 'toolbar'
1464+
})
1465+
@View({
1466+
template: 'TOOLBAR(<div *for="var part of query" [toolbar-vc]="part"></div>)',
1467+
directives: [ToolbarViewContainer, For]
1468+
})
1469+
class ToolbarComponent {
1470+
query:QueryList;
1471+
ctxProp:string;
1472+
1473+
constructor(@Query(ToolbarPart) query: QueryList) {
1474+
this.ctxProp = 'hello world';
1475+
this.query = query;
1476+
}
1477+
}

modules/angular2/test/core/compiler/view_container_ref_spec.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,20 @@ export function main() {
5555
location = new ElementRef(wrapView(view), 0);
5656
});
5757

58-
it('should return a 0 length if there is no underlying ViewContainerRef', () => {
59-
var vc = createViewContainer();
60-
expect(vc.length).toBe(0);
61-
});
58+
describe('length', () => {
59+
60+
it('should return a 0 length if there is no underlying ViewContainerRef', () => {
61+
var vc = createViewContainer();
62+
expect(vc.length).toBe(0);
63+
});
64+
65+
it('should return the size of the underlying ViewContainerRef', () => {
66+
var vc = createViewContainer();
67+
view.viewContainers = [new AppViewContainer()];
68+
view.viewContainers[0].views = [createView()];
69+
expect(vc.length).toBe(1);
70+
});
6271

63-
it('should return the size of the underlying ViewContainerRef', () => {
64-
var vc = createViewContainer();
65-
view.viewContainers = [new AppViewContainer()];
66-
view.viewContainers[0].views = [createView()];
67-
expect(vc.length).toBe(1);
6872
});
6973

7074
// TODO: add missing tests here!

modules/angular2/test/core/compiler/view_manager_spec.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export function main() {
133133
utils.spy('attachComponentView').andCallFake( (hostView, elementIndex, childView) => {
134134
hostView.componentChildViews[elementIndex] = childView;
135135
});
136-
utils.spy('attachViewInContainer').andCallFake( (parentView, elementIndex, atIndex, childView) => {
136+
utils.spy('attachViewInContainer').andCallFake( (parentView, elementIndex, _a, _b, atIndex, childView) => {
137137
var viewContainer = parentView.viewContainers[elementIndex];
138138
if (isBlank(viewContainer)) {
139139
viewContainer = new AppViewContainer();
@@ -411,26 +411,30 @@ export function main() {
411411
});
412412

413413
it('should attach the view', () => {
414-
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null)
415-
expect(utils.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, createdViews[0]);
414+
var contextView = createView();
415+
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView),
416+
elementRef(wrapView(contextView), 1), null);
417+
expect(utils.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, createdViews[0]);
416418
expect(renderer.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView.render, 0, 0, createdViews[0].render);
417419
});
418420

419421
it('should hydrate the view', () => {
420422
var injector = new Injector([], null, false);
421-
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), injector);
422-
expect(utils.spy('hydrateViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, injector);
423+
var contextView = createView();
424+
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView),
425+
elementRef(wrapView(contextView), 1), injector);
426+
expect(utils.spy('hydrateViewInContainer')).toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, injector);
423427
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
424428
});
425429

426430
it('should create and set the render view', () => {
427-
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null);
431+
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null, null);
428432
expect(renderer.spy('createView')).toHaveBeenCalledWith(childProtoView.render);
429433
expect(createdViews[0].render).toBe(createdRenderViews[0]);
430434
});
431435

432436
it('should set the event dispatcher', () => {
433-
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null);
437+
manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null, null);
434438
var childView = createdViews[0];
435439
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(childView.render, childView);
436440
});

0 commit comments

Comments
 (0)
X Tutup