diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts
index b8ae8a2faf..3d2c023f1a 100644
--- a/packages/core/src/render3/node_manipulation.ts
+++ b/packages/core/src/render3/node_manipulation.ts
@@ -145,7 +145,7 @@ function walkLNodeTree(
nextNode = head ? (componentHost.data as LViewData)[PARENT] ![head.index] : null;
} else {
// Otherwise look at the first child
- nextNode = getChildLNode(node as LViewNode);
+ nextNode = getChildLNode(node as LViewNode | LElementContainerNode);
}
if (nextNode === null) {
@@ -532,6 +532,16 @@ function canInsertNativeChildOfElement(parent: LElementNode, currentView: LViewD
return false;
}
+/**
+ * We might delay insertion of children for a given view if it is disconnected.
+ * This might happen for 2 main reason:
+ * - view is not inserted into any container (view was created but not iserted yet)
+ * - view is inserted into a container but the container itself is not inserted into the DOM
+ * (container might be part of projection or child of a view that is not inserted yet).
+ *
+ * In other words we can insert children of a given view this view was inserted into a container and
+ * the container itself has it render parent determined.
+ */
function canInsertNativeChildOfView(parent: LViewNode): boolean {
ngDevMode && assertNodeType(parent, TNodeType.View);
@@ -635,7 +645,10 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: LVi
nativeInsertBefore(renderer, renderParent !.native, child, beforeNode);
} else if (parent.tNode.type === TNodeType.ElementContainer) {
const beforeNode = parent.native;
- const grandParent = getParentLNode(parent) as LElementNode | LViewNode;
+ let grandParent = getParentLNode(parent as LElementContainerNode);
+ while (grandParent.tNode.type === TNodeType.ElementContainer) {
+ grandParent = getParentLNode(grandParent as LElementContainerNode);
+ }
if (grandParent.tNode.type === TNodeType.View) {
const renderParent = getRenderParent(grandParent as LViewNode);
nativeInsertBefore(renderer, renderParent !.native, child, beforeNode);
diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts
index c72b1c7953..7df1547fc5 100644
--- a/packages/core/test/render3/integration_spec.ts
+++ b/packages/core/test/render3/integration_spec.ts
@@ -651,6 +651,108 @@ describe('render3 integration test', () => {
expect(fixture.html).toEqual('component template');
});
+ it('should render inside another ng-container', () => {
+ /**
+ *
+ *
+ *
+ * content
+ *
+ *
+ *
+ */
+ const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags) {
+ if (rf & RenderFlags.Create) {
+ elementContainerStart(0);
+ {
+ elementContainerStart(1);
+ {
+ elementContainerStart(2);
+ { text(3, 'content'); }
+ elementContainerEnd();
+ }
+ elementContainerEnd();
+ }
+ elementContainerEnd();
+ }
+ });
+
+ function App() { element(0, 'test-cmpt'); }
+
+ const fixture = new TemplateFixture(App, () => {}, [TestCmpt]);
+ expect(fixture.html).toEqual('content');
+ });
+
+ it('should render inside another ng-container at the root of a delayed view', () => {
+
+ class TestDirective {
+ constructor(private _tplRef: TemplateRef, private _vcRef: ViewContainerRef) {}
+
+ createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
+
+ clear() { this._vcRef.clear(); }
+
+ static ngDirectiveDef = defineDirective({
+ type: TestDirective,
+ selectors: [['', 'testDirective', '']],
+ factory: () => new TestDirective(injectTemplateRef(), injectViewContainerRef()),
+ });
+ }
+
+ let testDirective: TestDirective;
+
+ function embeddedTemplate(rf: RenderFlags, ctx: any) {
+ if (rf & RenderFlags.Create) {
+ elementContainerStart(0);
+ {
+ elementContainerStart(1);
+ {
+ elementContainerStart(2);
+ { text(3, 'content'); }
+ elementContainerEnd();
+ }
+ elementContainerEnd();
+ }
+ elementContainerEnd();
+ }
+ }
+
+ `
+
+
+
+ content
+
+
+
+ `;
+ const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags) {
+ if (rf & RenderFlags.Create) {
+ container(0, embeddedTemplate, null, [AttributeMarker.SelectOnly, 'testDirective']);
+ }
+ if (rf & RenderFlags.Update) {
+ testDirective = loadDirective(0);
+ }
+ }, [TestDirective]);
+
+ function App() { element(0, 'test-cmpt'); }
+
+ const fixture = new ComponentFixture(TestCmpt);
+ expect(fixture.html).toEqual('');
+
+ testDirective !.createAndInsert();
+ fixture.update();
+ expect(fixture.html).toEqual('content');
+
+ testDirective !.createAndInsert();
+ fixture.update();
+ expect(fixture.html).toEqual('contentcontent');
+
+ testDirective !.clear();
+ fixture.update();
+ expect(fixture.html).toEqual('');
+ });
+
it('should support directives and inject ElementRef', () => {
class Directive {