diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts
index f42816ce94..5850a4fb5b 100644
--- a/packages/core/src/render3/node_manipulation.ts
+++ b/packages/core/src/render3/node_manipulation.ts
@@ -490,4 +490,7 @@ export function appendProjectedNode(
addRemoveViewFromContainer(node as LContainerNode, views[i], true, null);
}
}
+ if (node.dynamicLContainerNode) {
+ node.dynamicLContainerNode.data.renderParent = currentParent as LElementNode;
+ }
}
diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts
index 2e2693eed3..8dfb671fa7 100644
--- a/packages/core/test/render3/view_container_ref_spec.ts
+++ b/packages/core/test/render3/view_container_ref_spec.ts
@@ -6,19 +6,32 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Directive, TemplateRef, ViewContainerRef} from '../../src/core';
+import {Component, Directive, TemplateRef, ViewContainerRef} from '../../src/core';
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
import {defineComponent, defineDirective, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
-import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, text, textBinding} from '../../src/render3/instructions';
+import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
import {ComponentFixture, TemplateFixture} from './render_util';
describe('ViewContainerRef', () => {
+ let directiveInstance: DirectiveWithVCRef|null;
+
+ beforeEach(() => { directiveInstance = null; });
+
+ class DirectiveWithVCRef {
+ static ngDirectiveDef = defineDirective({
+ type: DirectiveWithVCRef,
+ selectors: [['', 'vcref', '']],
+ factory: () => directiveInstance = new DirectiveWithVCRef(injectViewContainerRef()),
+ inputs: {tplRef: 'tplRef'}
+ });
+
+ tplRef: TemplateRef<{}>;
+
+ constructor(public vcref: ViewContainerRef) {}
+ }
+
describe('API', () => {
- let directiveInstance: DirectiveWithVCRef|null;
-
- beforeEach(() => { directiveInstance = null; });
-
function embeddedTemplate(ctx: any, cm: boolean) {
if (cm) {
text(0);
@@ -26,19 +39,6 @@ describe('ViewContainerRef', () => {
textBinding(0, ctx.name);
}
- class DirectiveWithVCRef {
- static ngDirectiveDef = defineDirective({
- type: DirectiveWithVCRef,
- selectors: [['', 'vcref', '']],
- factory: () => directiveInstance = new DirectiveWithVCRef(injectViewContainerRef()),
- inputs: {tplRef: 'tplRef'}
- });
-
- tplRef: TemplateRef<{}>;
-
- constructor(public vcref: ViewContainerRef) {}
- }
-
function createView(s: string, index?: number) {
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, {name: s}, index);
}
@@ -452,4 +452,188 @@ describe('ViewContainerRef', () => {
});
});
});
+
+ describe('projection', () => {
+ function embeddedTemplate(ctx: any, cm: boolean) {
+ if (cm) {
+ elementStart(0, 'span');
+ text(1);
+ elementEnd();
+ }
+ textBinding(1, ctx.name);
+ }
+
+ it('should project the ViewContainerRef content along its host', () => {
+ @Component({selector: 'child', template: '
'})
+ class Child {
+ static ngComponentDef = defineComponent({
+ type: Child,
+ selectors: [['child']],
+ factory: () => new Child(),
+ template: (cmp: Child, cm: boolean) => {
+ if (cm) {
+ projectionDef(0);
+ elementStart(1, 'div');
+ { projection(2, 0); }
+ elementEnd();
+ }
+ }
+ });
+ }
+
+ @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');
+ 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: [Child, DirectiveWithVCRef]
+ });
+ }
+
+ const fixture = new ComponentFixture(Parent);
+ expect(fixture.html).toEqual('');
+
+ directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
+ fixture.update();
+ expect(fixture.html)
+ .toEqual('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}}
+
+ `
+ })
+ 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(
+ '');
+
+ directiveInstance !.vcref.createEmbeddedView(
+ directiveInstance !.tplRef, fixture.component);
+ fixture.update();
+ expect(fixture.html)
+ .toEqual(
+ '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(
+ 'bar');
+ });
+ });
});