diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index c0d357a861..a1013eff9b 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1831,48 +1831,11 @@ export function embeddedViewEnd(): void { refreshView(); isParent = false; previousOrParentNode = viewData[HOST_NODE] as LViewNode; - if (creationMode) { - const containerNode = getParentLNode(previousOrParentNode) as LContainerNode; - if (containerNode) { - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); - ngDevMode && assertNodeType(containerNode, TNodeType.Container); - // When projected nodes are going to be inserted, the renderParent of the dynamic container - // used by the ViewContainerRef must be set. - setRenderParentInProjectedNodes( - containerNode.data[RENDER_PARENT], previousOrParentNode as LViewNode); - } - } leaveView(viewData[PARENT] !); ngDevMode && assertEqual(isParent, false, 'isParent'); ngDevMode && assertNodeType(previousOrParentNode, TNodeType.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: LNode|null = getChildLNode(viewNode); - while (node) { - if (node.tNode.type === TNodeType.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[RENDER_PARENT] = renderParent; - } - nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent; - } - } - node = getNextLNode(node); - } - } -} - ///////////// /** @@ -1946,7 +1909,7 @@ export function projectionDef( // execute selector matching logic if and only if: // - there are selectors defined // - a node has a tag name / attributes that can be matched - if (selectors && componentChild.tNode) { + if (selectors) { const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !); distributedNodes[matchedIdx].push(componentChild); } else { @@ -2037,10 +2000,14 @@ export function projection( // process each node in the list of projected nodes: let nodeToProject: LNode|null = node.data.head; const lastNodeToProject = node.data.tail; + const renderParent = currentParent.tNode.type === TNodeType.View ? + (getParentLNode(currentParent) as LContainerNode).data[RENDER_PARENT] ! : + currentParent as LElementNode; + while (nodeToProject) { appendProjectedNode( - nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent as LElementNode, - viewData); + nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent, viewData, + renderParent); nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent; } } diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 9f64c77d4a..4806de5499 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -608,24 +608,24 @@ export function removeChild(parent: LNode, child: RNode | null, currentView: LVi * @param currentView Current LView */ export function appendProjectedNode( - node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode, - currentView: LViewData): void { + node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode | LViewNode, + currentView: LViewData, renderParent: LElementNode): void { appendChild(currentParent, node.native, currentView); if (node.tNode.type === TNodeType.Container) { - // The node we are adding is a Container and we are adding it to Element which + // The node we are adding is a container and we are adding it to an element which // is not a component (no more re-projection). // Alternatively a container is projected at the root of a component's template // and can't be re-projected (as not content of any component). - // Assignee the final projection location in those cases. + // Assign the final projection location in those cases. const lContainer = (node as LContainerNode).data; - lContainer[RENDER_PARENT] = currentParent; + lContainer[RENDER_PARENT] = renderParent; const views = lContainer[VIEWS]; for (let i = 0; i < views.length; i++) { addRemoveViewFromContainer(node as LContainerNode, views[i], true, null); } } if (node.dynamicLContainerNode) { - node.dynamicLContainerNode.data[RENDER_PARENT] = currentParent; + node.dynamicLContainerNode.data[RENDER_PARENT] = renderParent; appendChild(currentParent, node.dynamicLContainerNode.native, currentView); } } diff --git a/packages/core/test/render3/content_spec.ts b/packages/core/test/render3/content_spec.ts index 058be8ca4b..a6c3316285 100644 --- a/packages/core/test/render3/content_spec.ts +++ b/packages/core/test/render3/content_spec.ts @@ -138,6 +138,7 @@ describe('content projection', () => { }); it('should project content with container.', () => { + /**
*/ const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { projectionDef(0); @@ -146,6 +147,16 @@ describe('content projection', () => { elementEnd(); } }); + + /** + * + * ( + * % if (value) { + * content + * % } + * ) + * + */ const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) { if (rf & RenderFlags.Create) { elementStart(0, 'child'); @@ -182,12 +193,21 @@ describe('content projection', () => { }); it('should project content with container into root', () => { + /** */ const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { projectionDef(0); projection(1, 0); } }); + + /** + * + * % if (value) { + * content + * % } + * + */ const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) { if (rf & RenderFlags.Create) { elementStart(0, 'child'); @@ -222,6 +242,7 @@ describe('content projection', () => { }); it('should project content with container and if-else.', () => { + /**
*/ const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { projectionDef(0); @@ -230,6 +251,18 @@ describe('content projection', () => { elementEnd(); } }); + + /** + * + * ( + * % if (value) { + * content + * % } else { + * else + * % } + * ) + * + */ const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) { if (rf & RenderFlags.Create) { elementStart(0, 'child'); @@ -270,7 +303,7 @@ describe('content projection', () => { expect(toHtml(parent)).toEqual('
(else)
'); }); - it('should support projection in embedded views', () => { + it('should support projection into embedded views', () => { let childCmptInstance: any; /** @@ -306,9 +339,7 @@ describe('content projection', () => { } }); - /** - * content - */ + /** content */ const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementStart(0, 'child'); @@ -383,6 +414,72 @@ describe('content projection', () => { expect(toHtml(parent)).toEqual('
'); }); + it('should project containers into embedded views', () => { + /** + *
+ * % if (!skipContent) { + * + * % } + *
+ */ + const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + projectionDef(0); + elementStart(1, 'div'); + { container(2); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(2); + { + if (!ctx.skipContent) { + let rf0 = embeddedViewStart(0); + if (rf0 & RenderFlags.Create) { + projection(0, 0); + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + }); + + /** + * + * % if (!skipContent) { + * content + * % } + * + */ + const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'child'); + { container(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + { + if (!ctx.skipContent) { + let rf0 = embeddedViewStart(0); + if (rf0 & RenderFlags.Create) { + text(0, 'content'); + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + }, [Child]); + + const fixture = new ComponentFixture(Parent); + expect(fixture.html).toEqual('
content
'); + + fixture.component.skipContent = true; + fixture.update(); + expect(fixture.html).toEqual('
'); + }); + it('should support projection in embedded views when ng-content is a root node of an embedded view, with other nodes after', () => { let childCmptInstance: any;