fix(ivy): support ViewContainerRef on nodes projected into an embedded view (#23333)
PR Close #23333
This commit is contained in:
parent
6199ea5d4a
commit
2bb783824e
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////
|
||||
|
||||
/**
|
||||
|
|
|
@ -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: '<div><ng-content></ng-content></div>'})
|
||||
class Child {
|
||||
static ngComponentDef = defineComponent({
|
||||
|
@ -532,6 +532,86 @@ describe('ViewContainerRef', () => {
|
|||
.toEqual('<child><div><header vcref="">blah</header><span>bar</span></div></child>');
|
||||
});
|
||||
|
||||
it('should project the ViewContainerRef content along its host, in a view', () => {
|
||||
@Component({
|
||||
selector: 'child-with-view',
|
||||
template: `
|
||||
% if (show) {
|
||||
<ng-content></ng-content>
|
||||
% }`
|
||||
})
|
||||
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: `
|
||||
<ng-template #foo>
|
||||
<span>{{name}}</span>
|
||||
</ng-template>
|
||||
<child-with-view>
|
||||
<header vcref [tplRef]="foo" [name]="name">blah</header>
|
||||
</child-with-view>`
|
||||
})
|
||||
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('<child-with-view><header vcref="">blah</header></child-with-view>');
|
||||
|
||||
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
|
||||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<child-with-view><header vcref="">blah</header><span>bar</span></child-with-view>');
|
||||
});
|
||||
|
||||
describe('with select', () => {
|
||||
@Component({
|
||||
selector: 'child-with-selector',
|
||||
|
|
Loading…
Reference in New Issue