From 2bb783824efa71fa32d7a5af535ce61b8d371600 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Thu, 12 Apr 2018 12:13:39 +0200 Subject: [PATCH] fix(ivy): support ViewContainerRef on nodes projected into an embedded view (#23333) PR Close #23333 --- packages/core/src/render3/instructions.ts | 29 +++++++ .../test/render3/view_container_ref_spec.ts | 82 ++++++++++++++++++- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 593577b095..ded6568583 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1563,6 +1563,9 @@ export function embeddedViewEnd(): void { const lContainer = containerNode.data; if (creationMode) { + // When projected nodes are going to be inserted, the renderParent of the dynamic container + // used by the ViewContainerRef must be set. + setRenderParentInProjectedNodes(lContainer.renderParent, viewNode); // it is a new view, insert it into collection of views for a given container insertView(containerNode, viewNode, lContainer.nextIndex); } @@ -1574,6 +1577,32 @@ export function embeddedViewEnd(): void { ngDevMode && assertNodeType(previousOrParentNode, LNodeType.View); } +/** + * For nodes which are projected inside an embedded view, this function sets the renderParent + * of their dynamic LContainerNode. + * @param renderParent the renderParent of the LContainer which contains the embedded view. + * @param viewNode the embedded view. + */ +function setRenderParentInProjectedNodes( + renderParent: LElementNode | null, viewNode: LViewNode): void { + if (renderParent != null) { + let node = viewNode.child; + while (node) { + if (node.type === LNodeType.Projection) { + let nodeToProject: LNode|null = (node as LProjectionNode).data.head; + const lastNodeToProject = (node as LProjectionNode).data.tail; + while (nodeToProject) { + if (nodeToProject.dynamicLContainerNode) { + nodeToProject.dynamicLContainerNode.data.renderParent = renderParent; + } + nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent; + } + } + node = node.next; + } + } +} + ///////////// /** diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 4b49204b57..a79c7ba365 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -471,7 +471,7 @@ describe('ViewContainerRef', () => { textBinding(1, ctx.name); } - it('should project the ViewContainerRef content along its host', () => { + it('should project the ViewContainerRef content along its host, in an element', () => { @Component({selector: 'child', template: '
'}) class Child { static ngComponentDef = defineComponent({ @@ -532,6 +532,86 @@ describe('ViewContainerRef', () => { .toEqual('
blah
bar
'); }); + it('should project the ViewContainerRef content along its host, in a view', () => { + @Component({ + selector: 'child-with-view', + template: ` + % if (show) { + + % }` + }) + class ChildWithView { + show: boolean = true; + static ngComponentDef = defineComponent({ + type: ChildWithView, + selectors: [['child-with-view']], + factory: () => new ChildWithView(), + template: (rf: RenderFlags, cmp: ChildWithView) => { + if (rf & RenderFlags.Create) { + projectionDef(0); + container(1); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + if (cmp.show) { + let rf0 = embeddedViewStart(0); + if (rf0 & RenderFlags.Create) { + projection(0, 0); + } + embeddedViewEnd(); + } + containerRefreshEnd(); + } + } + }); + } + + @Component({ + selector: 'parent', + template: ` + + {{name}} + + +
blah
+
` + }) + class Parent { + name: string = 'bar'; + static ngComponentDef = defineComponent({ + type: Parent, + selectors: [['parent']], + factory: () => new Parent(), + template: (rf: RenderFlags, cmp: Parent) => { + if (rf & RenderFlags.Create) { + container(0, embeddedTemplate); + elementStart(1, 'child-with-view'); + elementStart(2, 'header', ['vcref', '']); + text(3, 'blah'); + elementEnd(); + elementEnd(); + } + if (rf & RenderFlags.Update) { + const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); + elementProperty(2, 'tplRef', bind(tplRef)); + elementProperty(2, 'name', bind(cmp.name)); + } + }, + directives: [ChildWithView, DirectiveWithVCRef] + }); + } + + const fixture = new ComponentFixture(Parent); + expect(fixture.html) + .toEqual('
blah
'); + + directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component); + fixture.update(); + expect(fixture.html) + .toEqual( + '
blah
bar
'); + }); + describe('with select', () => { @Component({ selector: 'child-with-selector',