diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts
index 185e651f32..3725b08f7b 100644
--- a/packages/core/src/render3/instructions.ts
+++ b/packages/core/src/render3/instructions.ts
@@ -1963,12 +1963,9 @@ 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) {
- const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !);
- distributedNodes[matchedIdx].push(componentChild);
- } else {
- distributedNodes[0].push(componentChild);
- }
+ const bucketIndex =
+ selectors ? matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !) : 0;
+ distributedNodes[bucketIndex].push(componentChild);
componentChild = getNextLNode(componentChild);
}
@@ -2031,8 +2028,10 @@ export function projection(
const currentParent = getParentLNode(node);
const canInsert = canInsertNativeNode(currentParent, viewData);
+ let grandparent: LContainerNode;
const renderParent = currentParent.tNode.type === TNodeType.View ?
- (getParentLNode(currentParent) as LContainerNode).data[RENDER_PARENT] ! :
+ (grandparent = getParentLNode(currentParent) as LContainerNode) &&
+ grandparent.data[RENDER_PARENT] ! :
currentParent as LElementNode;
for (let i = 0; i < nodesForSelector.length; i++) {
diff --git a/packages/core/test/render3/content_spec.ts b/packages/core/test/render3/content_spec.ts
index 8f16a1fa00..2944e11d85 100644
--- a/packages/core/test/render3/content_spec.ts
+++ b/packages/core/test/render3/content_spec.ts
@@ -706,6 +706,171 @@ describe('content projection', () => {
expect(toHtml(parent)).toEqual('');
});
+ it('should project into dynamic views (with createEmbeddedView)', () => {
+ class NgIf {
+ constructor(public vcr: ViewContainerRef, public template: TemplateRef) {}
+
+ @Input()
+ set ngIf(value: boolean) {
+ value ? this.vcr.createEmbeddedView(this.template) : this.vcr.clear();
+ }
+
+ static ngDirectiveDef = defineDirective({
+ type: NgIf,
+ selectors: [['', 'ngIf', '']],
+ inputs: {'ngIf': 'ngIf'},
+ factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef())
+ });
+ }
+
+ /**
+ * Before-
+ *
+ *
+ *
+ * -After
+ */
+ const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
+ if (rf & RenderFlags.Create) {
+ projectionDef(0);
+ text(1, 'Before-');
+ container(2, IfTemplate, '', [AttributeMarker.SelectOnly, 'ngIf']);
+ text(3, '-After');
+ }
+ if (rf & RenderFlags.Update) {
+ elementProperty(2, 'ngIf', bind(ctx.showing));
+ }
+
+ function IfTemplate(rf1: RenderFlags, ctx1: any) {
+ if (rf1 & RenderFlags.Create) {
+ projectionDef(0);
+ projection(1, 0);
+ }
+ }
+ }, [NgIf]);
+
+ let child: {showing: boolean};
+ /**
+ *
+ * A
+ * Some text
+ *
+ */
+ const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
+ if (rf & RenderFlags.Create) {
+ elementStart(0, 'child');
+ {
+ elementStart(1, 'div');
+ { text(2, 'A'); }
+ elementEnd();
+ text(3, 'Some text');
+ }
+ elementEnd();
+
+ // testing
+ child = loadDirective(0);
+ }
+ }, [Child]);
+
+ const fixture = new ComponentFixture(App);
+ child !.showing = true;
+ fixture.update();
+ expect(fixture.html).toEqual('Before-A
Some text-After');
+
+ child !.showing = false;
+ fixture.update();
+ expect(fixture.html).toEqual('Before--After');
+
+ child !.showing = true;
+ fixture.update();
+ expect(fixture.html).toEqual('Before-A
Some text-After');
+ });
+
+ it('should project into dynamic views (with insertion)', () => {
+ class NgIf {
+ constructor(public vcr: ViewContainerRef, public template: TemplateRef) {}
+
+ @Input()
+ set ngIf(value: boolean) {
+ if (value) {
+ const viewRef = this.template.createEmbeddedView({});
+ this.vcr.insert(viewRef);
+ } else {
+ this.vcr.clear();
+ }
+ }
+
+ static ngDirectiveDef = defineDirective({
+ type: NgIf,
+ selectors: [['', 'ngIf', '']],
+ inputs: {'ngIf': 'ngIf'},
+ factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef())
+ });
+ }
+
+ /**
+ * Before-
+ *
+ *
+ *
+ * -After
+ */
+ const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
+ if (rf & RenderFlags.Create) {
+ projectionDef(0);
+ text(1, 'Before-');
+ container(2, IfTemplate, '', [AttributeMarker.SelectOnly, 'ngIf']);
+ text(3, '-After');
+ }
+ if (rf & RenderFlags.Update) {
+ elementProperty(2, 'ngIf', bind(ctx.showing));
+ }
+
+ function IfTemplate(rf1: RenderFlags, ctx1: any) {
+ if (rf1 & RenderFlags.Create) {
+ projectionDef(0);
+ projection(1, 0);
+ }
+ }
+ }, [NgIf]);
+
+ let child: {showing: boolean};
+ /**
+ *
+ * A
+ * Some text
+ *
+ */
+ const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
+ if (rf & RenderFlags.Create) {
+ elementStart(0, 'child');
+ {
+ elementStart(1, 'div');
+ { text(2, 'A'); }
+ elementEnd();
+ text(3, 'Some text');
+ }
+ elementEnd();
+
+ // testing
+ child = loadDirective(0);
+ }
+ }, [Child]);
+
+ const fixture = new ComponentFixture(App);
+ child !.showing = true;
+ fixture.update();
+ expect(fixture.html).toEqual('Before-A
Some text-After');
+
+ child !.showing = false;
+ fixture.update();
+ expect(fixture.html).toEqual('Before--After');
+
+ child !.showing = true;
+ fixture.update();
+ expect(fixture.html).toEqual('Before-A
Some text-After');
+ });
+
it('should project nodes into the last ng-content', () => {
/**
*