fix(ivy): support projecting containers into containers (#24695)
PR Close #24695
This commit is contained in:
parent
3db9d57de3
commit
99bdd257a6
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,6 +138,7 @@ describe('content projection', () => {
|
|||
});
|
||||
|
||||
it('should project content with container.', () => {
|
||||
/** <div> <ng-content></ng-content></div> */
|
||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
projectionDef(0);
|
||||
|
@ -146,6 +147,16 @@ describe('content projection', () => {
|
|||
elementEnd();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* <child>
|
||||
* (
|
||||
* % if (value) {
|
||||
* content
|
||||
* % }
|
||||
* )
|
||||
* </child>
|
||||
*/
|
||||
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', () => {
|
||||
/** <ng-content></ng-content> */
|
||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
projectionDef(0);
|
||||
projection(1, 0);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* <child>
|
||||
* % if (value) {
|
||||
* content
|
||||
* % }
|
||||
* </child>
|
||||
*/
|
||||
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.', () => {
|
||||
/** <div><ng-content></ng-content></div> */
|
||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
projectionDef(0);
|
||||
|
@ -230,6 +251,18 @@ describe('content projection', () => {
|
|||
elementEnd();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* <child>
|
||||
* (
|
||||
* % if (value) {
|
||||
* content
|
||||
* % } else {
|
||||
* else
|
||||
* % }
|
||||
* )
|
||||
* </child>
|
||||
*/
|
||||
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('<child><div>(else)</div></child>');
|
||||
});
|
||||
|
||||
it('should support projection in embedded views', () => {
|
||||
it('should support projection into embedded views', () => {
|
||||
let childCmptInstance: any;
|
||||
|
||||
/**
|
||||
|
@ -306,9 +339,7 @@ describe('content projection', () => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* <child>content</child>
|
||||
*/
|
||||
/** <child>content</child> */
|
||||
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('<child><div></div></child>');
|
||||
});
|
||||
|
||||
it('should project containers into embedded views', () => {
|
||||
/**
|
||||
* <div>
|
||||
* % if (!skipContent) {
|
||||
* <ng-content></ng-content>
|
||||
* % }
|
||||
* </div>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* <child>
|
||||
* % if (!skipContent) {
|
||||
* content
|
||||
* % }
|
||||
* </child>
|
||||
*/
|
||||
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('<child><div>content</div></child>');
|
||||
|
||||
fixture.component.skipContent = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<child><div></div></child>');
|
||||
});
|
||||
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue