Skip to content

Commit 99bdd25

Browse files
karamhevery
authored andcommitted
fix(ivy): support projecting containers into containers (angular#24695)
PR Close angular#24695
1 parent 3db9d57 commit 99bdd25

File tree

3 files changed

+114
-50
lines changed

3 files changed

+114
-50
lines changed

packages/core/src/render3/instructions.ts

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,48 +1831,11 @@ export function embeddedViewEnd(): void {
18311831
refreshView();
18321832
isParent = false;
18331833
previousOrParentNode = viewData[HOST_NODE] as LViewNode;
1834-
if (creationMode) {
1835-
const containerNode = getParentLNode(previousOrParentNode) as LContainerNode;
1836-
if (containerNode) {
1837-
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View);
1838-
ngDevMode && assertNodeType(containerNode, TNodeType.Container);
1839-
// When projected nodes are going to be inserted, the renderParent of the dynamic container
1840-
// used by the ViewContainerRef must be set.
1841-
setRenderParentInProjectedNodes(
1842-
containerNode.data[RENDER_PARENT], previousOrParentNode as LViewNode);
1843-
}
1844-
}
18451834
leaveView(viewData[PARENT] !);
18461835
ngDevMode && assertEqual(isParent, false, 'isParent');
18471836
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View);
18481837
}
18491838

1850-
/**
1851-
* For nodes which are projected inside an embedded view, this function sets the renderParent
1852-
* of their dynamic LContainerNode.
1853-
* @param renderParent the renderParent of the LContainer which contains the embedded view.
1854-
* @param viewNode the embedded view.
1855-
*/
1856-
function setRenderParentInProjectedNodes(
1857-
renderParent: LElementNode | null, viewNode: LViewNode): void {
1858-
if (renderParent != null) {
1859-
let node: LNode|null = getChildLNode(viewNode);
1860-
while (node) {
1861-
if (node.tNode.type === TNodeType.Projection) {
1862-
let nodeToProject: LNode|null = (node as LProjectionNode).data.head;
1863-
const lastNodeToProject = (node as LProjectionNode).data.tail;
1864-
while (nodeToProject) {
1865-
if (nodeToProject.dynamicLContainerNode) {
1866-
nodeToProject.dynamicLContainerNode.data[RENDER_PARENT] = renderParent;
1867-
}
1868-
nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent;
1869-
}
1870-
}
1871-
node = getNextLNode(node);
1872-
}
1873-
}
1874-
}
1875-
18761839
/////////////
18771840

18781841
/**
@@ -1946,7 +1909,7 @@ export function projectionDef(
19461909
// execute selector matching logic if and only if:
19471910
// - there are selectors defined
19481911
// - a node has a tag name / attributes that can be matched
1949-
if (selectors && componentChild.tNode) {
1912+
if (selectors) {
19501913
const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !);
19511914
distributedNodes[matchedIdx].push(componentChild);
19521915
} else {
@@ -2037,10 +2000,14 @@ export function projection(
20372000
// process each node in the list of projected nodes:
20382001
let nodeToProject: LNode|null = node.data.head;
20392002
const lastNodeToProject = node.data.tail;
2003+
const renderParent = currentParent.tNode.type === TNodeType.View ?
2004+
(getParentLNode(currentParent) as LContainerNode).data[RENDER_PARENT] ! :
2005+
currentParent as LElementNode;
2006+
20402007
while (nodeToProject) {
20412008
appendProjectedNode(
2042-
nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent as LElementNode,
2043-
viewData);
2009+
nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent, viewData,
2010+
renderParent);
20442011
nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent;
20452012
}
20462013
}

packages/core/src/render3/node_manipulation.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -608,24 +608,24 @@ export function removeChild(parent: LNode, child: RNode | null, currentView: LVi
608608
* @param currentView Current LView
609609
*/
610610
export function appendProjectedNode(
611-
node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode,
612-
currentView: LViewData): void {
611+
node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode | LViewNode,
612+
currentView: LViewData, renderParent: LElementNode): void {
613613
appendChild(currentParent, node.native, currentView);
614614
if (node.tNode.type === TNodeType.Container) {
615-
// The node we are adding is a Container and we are adding it to Element which
615+
// The node we are adding is a container and we are adding it to an element which
616616
// is not a component (no more re-projection).
617617
// Alternatively a container is projected at the root of a component's template
618618
// and can't be re-projected (as not content of any component).
619-
// Assignee the final projection location in those cases.
619+
// Assign the final projection location in those cases.
620620
const lContainer = (node as LContainerNode).data;
621-
lContainer[RENDER_PARENT] = currentParent;
621+
lContainer[RENDER_PARENT] = renderParent;
622622
const views = lContainer[VIEWS];
623623
for (let i = 0; i < views.length; i++) {
624624
addRemoveViewFromContainer(node as LContainerNode, views[i], true, null);
625625
}
626626
}
627627
if (node.dynamicLContainerNode) {
628-
node.dynamicLContainerNode.data[RENDER_PARENT] = currentParent;
628+
node.dynamicLContainerNode.data[RENDER_PARENT] = renderParent;
629629
appendChild(currentParent, node.dynamicLContainerNode.native, currentView);
630630
}
631631
}

packages/core/test/render3/content_spec.ts

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ describe('content projection', () => {
138138
});
139139

140140
it('should project content with container.', () => {
141+
/** <div> <ng-content></ng-content></div> */
141142
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
142143
if (rf & RenderFlags.Create) {
143144
projectionDef(0);
@@ -146,6 +147,16 @@ describe('content projection', () => {
146147
elementEnd();
147148
}
148149
});
150+
151+
/**
152+
* <child>
153+
* (
154+
* % if (value) {
155+
* content
156+
* % }
157+
* )
158+
* </child>
159+
*/
149160
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) {
150161
if (rf & RenderFlags.Create) {
151162
elementStart(0, 'child');
@@ -182,12 +193,21 @@ describe('content projection', () => {
182193
});
183194

184195
it('should project content with container into root', () => {
196+
/** <ng-content></ng-content> */
185197
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
186198
if (rf & RenderFlags.Create) {
187199
projectionDef(0);
188200
projection(1, 0);
189201
}
190202
});
203+
204+
/**
205+
* <child>
206+
* % if (value) {
207+
* content
208+
* % }
209+
* </child>
210+
*/
191211
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) {
192212
if (rf & RenderFlags.Create) {
193213
elementStart(0, 'child');
@@ -222,6 +242,7 @@ describe('content projection', () => {
222242
});
223243

224244
it('should project content with container and if-else.', () => {
245+
/** <div><ng-content></ng-content></div> */
225246
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
226247
if (rf & RenderFlags.Create) {
227248
projectionDef(0);
@@ -230,6 +251,18 @@ describe('content projection', () => {
230251
elementEnd();
231252
}
232253
});
254+
255+
/**
256+
* <child>
257+
* (
258+
* % if (value) {
259+
* content
260+
* % } else {
261+
* else
262+
* % }
263+
* )
264+
* </child>
265+
*/
233266
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) {
234267
if (rf & RenderFlags.Create) {
235268
elementStart(0, 'child');
@@ -270,7 +303,7 @@ describe('content projection', () => {
270303
expect(toHtml(parent)).toEqual('<child><div>(else)</div></child>');
271304
});
272305

273-
it('should support projection in embedded views', () => {
306+
it('should support projection into embedded views', () => {
274307
let childCmptInstance: any;
275308

276309
/**
@@ -306,9 +339,7 @@ describe('content projection', () => {
306339
}
307340
});
308341

309-
/**
310-
* <child>content</child>
311-
*/
342+
/** <child>content</child> */
312343
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
313344
if (rf & RenderFlags.Create) {
314345
elementStart(0, 'child');
@@ -383,6 +414,72 @@ describe('content projection', () => {
383414
expect(toHtml(parent)).toEqual('<child><div></div></child>');
384415
});
385416

417+
it('should project containers into embedded views', () => {
418+
/**
419+
* <div>
420+
* % if (!skipContent) {
421+
* <ng-content></ng-content>
422+
* % }
423+
* </div>
424+
*/
425+
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
426+
if (rf & RenderFlags.Create) {
427+
projectionDef(0);
428+
elementStart(1, 'div');
429+
{ container(2); }
430+
elementEnd();
431+
}
432+
if (rf & RenderFlags.Update) {
433+
containerRefreshStart(2);
434+
{
435+
if (!ctx.skipContent) {
436+
let rf0 = embeddedViewStart(0);
437+
if (rf0 & RenderFlags.Create) {
438+
projection(0, 0);
439+
}
440+
embeddedViewEnd();
441+
}
442+
}
443+
containerRefreshEnd();
444+
}
445+
});
446+
447+
/**
448+
* <child>
449+
* % if (!skipContent) {
450+
* content
451+
* % }
452+
* </child>
453+
*/
454+
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
455+
if (rf & RenderFlags.Create) {
456+
elementStart(0, 'child');
457+
{ container(1); }
458+
elementEnd();
459+
}
460+
if (rf & RenderFlags.Update) {
461+
containerRefreshStart(1);
462+
{
463+
if (!ctx.skipContent) {
464+
let rf0 = embeddedViewStart(0);
465+
if (rf0 & RenderFlags.Create) {
466+
text(0, 'content');
467+
}
468+
embeddedViewEnd();
469+
}
470+
}
471+
containerRefreshEnd();
472+
}
473+
}, [Child]);
474+
475+
const fixture = new ComponentFixture(Parent);
476+
expect(fixture.html).toEqual('<child><div>content</div></child>');
477+
478+
fixture.component.skipContent = true;
479+
fixture.update();
480+
expect(fixture.html).toEqual('<child><div></div></child>');
481+
});
482+
386483
it('should support projection in embedded views when ng-content is a root node of an embedded view, with other nodes after',
387484
() => {
388485
let childCmptInstance: any;

0 commit comments

Comments
 (0)