fix(ivy): support ViewContainerRef on nodes projected into an embedded view (#23333)

PR Close #23333
This commit is contained in:
Marc Laval 2018-04-12 12:13:39 +02:00 committed by Igor Minar
parent 6199ea5d4a
commit 2bb783824e
2 changed files with 110 additions and 1 deletions

View File

@ -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;
}
}
}
/////////////
/**

View File

@ -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',