diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 5cd5377caa..b1ff51a1ed 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1710,12 +1710,14 @@ export function projection( } if (canInsertNativeNode(currentParent, currentView)) { + ngDevMode && assertNodeType(currentParent, LNodeType.Element); // process each node in the list of projected nodes: let nodeToProject: LNode|null = node.data.head; const lastNodeToProject = node.data.tail; while (nodeToProject) { appendProjectedNode( - nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent, currentView); + nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent as LElementNode, + currentView); 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 5850a4fb5b..4aaa93d12e 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -473,24 +473,24 @@ export function insertChild(node: LNode, currentView: LView): void { * @param currentView Current LView */ export function appendProjectedNode( - node: LElementNode | LTextNode | LContainerNode, currentParent: LViewNode | LElementNode, + node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode, currentView: LView): void { if (node.type !== LNodeType.Container) { appendChild(currentParent, (node as LElementNode | LTextNode).native, currentView); - } else if (canInsertNativeNode(currentParent, currentView)) { + } else { // The node we are adding is a Container and we are adding it to 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. const lContainer = (node as LContainerNode).data; - lContainer.renderParent = currentParent as LElementNode; + lContainer.renderParent = currentParent; 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.renderParent = currentParent as LElementNode; + node.dynamicLContainerNode.data.renderParent = currentParent; } } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 8dfb671fa7..a1b0a0fadf 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -28,6 +28,8 @@ describe('ViewContainerRef', () => { tplRef: TemplateRef<{}>; + // injecting a ViewContainerRef to create a dynamic container in which embedded views will be + // created constructor(public vcref: ViewContainerRef) {} } @@ -521,119 +523,123 @@ describe('ViewContainerRef', () => { .toEqual('
blah
bar
'); }); - @Component({ - selector: 'child-with-selector', - template: ` - - ` - }) - class ChildWithSelector { - static ngComponentDef = defineComponent({ - type: ChildWithSelector, - selectors: [['child-with-selector']], - factory: () => new ChildWithSelector(), - template: (cmp: ChildWithSelector, cm: boolean) => { - if (cm) { - projectionDef(0, [[['header']]], ['header']); - elementStart(1, 'first'); - { projection(2, 0, 1); } - elementEnd(); - elementStart(3, 'second'); - { projection(4, 0); } - elementEnd(); - } - } - }); - } - - it('should project the ViewContainerRef content along its host, when the host matches a selector', - () => { - @Component({ - selector: 'parent', - template: ` - - {{name}} - -
blah
` - }) - class Parent { - name: string = 'bar'; - static ngComponentDef = defineComponent({ - type: Parent, - selectors: [['parent']], - factory: () => new Parent(), - template: (cmp: Parent, cm: boolean) => { - if (cm) { - container(0, embeddedTemplate); - elementStart(1, 'child-with-selector'); - elementStart(2, 'header', ['vcref', '']); - text(3, 'blah'); - elementEnd(); - elementEnd(); - } - const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); - elementProperty(2, 'tplRef', bind(tplRef)); - elementProperty(2, 'name', bind(cmp.name)); - }, - directives: [ChildWithSelector, DirectiveWithVCRef] - }); - } - - const fixture = new ComponentFixture(Parent); - expect(fixture.html) - .toEqual( - '
blah
'); - - directiveInstance !.vcref.createEmbeddedView( - directiveInstance !.tplRef, fixture.component); - fixture.update(); - expect(fixture.html) - .toEqual( - '
blah
bar
'); - }); - - it('should not project the ViewContainerRef content, when the host does not match a selector', () => { + describe('with select', () => { @Component({ - selector: 'parent', + selector: 'child-with-selector', template: ` - - {{name}} - - ` + + ` }) - class Parent { - name: string = 'bar'; + class ChildWithSelector { static ngComponentDef = defineComponent({ - type: Parent, - selectors: [['parent']], - factory: () => new Parent(), - template: (cmp: Parent, cm: boolean) => { + type: ChildWithSelector, + selectors: [['child-with-selector']], + factory: () => new ChildWithSelector(), + template: (cmp: ChildWithSelector, cm: boolean) => { if (cm) { - container(0, embeddedTemplate); - elementStart(1, 'child-with-selector'); - elementStart(2, 'footer', ['vcref', '']); - text(3, 'blah'); + projectionDef(0, [[['header']]], ['header']); + elementStart(1, 'first'); + { projection(2, 0, 1); } elementEnd(); + elementStart(3, 'second'); + { projection(4, 0); } elementEnd(); } - const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); - elementProperty(2, 'tplRef', bind(tplRef)); - elementProperty(2, 'name', bind(cmp.name)); - }, - directives: [ChildWithSelector, DirectiveWithVCRef] + } }); } - const fixture = new ComponentFixture(Parent); - expect(fixture.html) - .toEqual( - ''); + it('should project the ViewContainerRef content along its host, when the host matches a selector', + () => { + @Component({ + selector: 'parent', + template: ` + + {{name}} + +
blah
` + }) + class Parent { + name: string = 'bar'; + static ngComponentDef = defineComponent({ + type: Parent, + selectors: [['parent']], + factory: () => new Parent(), + template: (cmp: Parent, cm: boolean) => { + if (cm) { + container(0, embeddedTemplate); + elementStart(1, 'child-with-selector'); + elementStart(2, 'header', ['vcref', '']); + text(3, 'blah'); + elementEnd(); + elementEnd(); + } + const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); + elementProperty(2, 'tplRef', bind(tplRef)); + elementProperty(2, 'name', bind(cmp.name)); + }, + directives: [ChildWithSelector, DirectiveWithVCRef] + }); + } - directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component); - fixture.update(); - expect(fixture.html) - .toEqual( - '
blah
bar
'); + const fixture = new ComponentFixture(Parent); + expect(fixture.html) + .toEqual( + '
blah
'); + + directiveInstance !.vcref.createEmbeddedView( + directiveInstance !.tplRef, fixture.component); + fixture.update(); + expect(fixture.html) + .toEqual( + '
blah
bar
'); + }); + + it('should not project the ViewContainerRef content, when the host does not match a selector', + () => { + @Component({ + selector: 'parent', + template: ` + + {{name}} + + ` + }) + class Parent { + name: string = 'bar'; + static ngComponentDef = defineComponent({ + type: Parent, + selectors: [['parent']], + factory: () => new Parent(), + template: (cmp: Parent, cm: boolean) => { + if (cm) { + container(0, embeddedTemplate); + elementStart(1, 'child-with-selector'); + elementStart(2, 'footer', ['vcref', '']); + text(3, 'blah'); + elementEnd(); + elementEnd(); + } + const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); + elementProperty(2, 'tplRef', bind(tplRef)); + elementProperty(2, 'name', bind(cmp.name)); + }, + directives: [ChildWithSelector, DirectiveWithVCRef] + }); + } + + const fixture = new ComponentFixture(Parent); + expect(fixture.html) + .toEqual( + ''); + + directiveInstance !.vcref.createEmbeddedView( + directiveInstance !.tplRef, fixture.component); + fixture.update(); + expect(fixture.html) + .toEqual( + '
blah
bar
'); + }); }); }); });