diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 24860481ea..d5a9b3a84f 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -590,6 +590,7 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer lContainerNode.tNode = hostTNode.dynamicContainerNode; vcRefHost.dynamicLContainerNode = lContainerNode; + lContainerNode.dynamicParent = vcRefHost; addToViewTree(vcRefHost.view, hostTNode.index as number, lContainer); @@ -649,6 +650,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { (viewRef as EmbeddedViewRef).attachToViewContainerRef(this); insertView(this._lContainerNode, lViewNode, adjustedIdx); + lViewNode.dynamicParent = this._lContainerNode; this._viewRefs.splice(adjustedIdx, 0, viewRef); @@ -672,7 +674,8 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { detach(index?: number): viewEngine_ViewRef|null { const adjustedIdx = this._adjustIndex(index, -1); - detachView(this._lContainerNode, adjustedIdx); + const lViewNode = detachView(this._lContainerNode, adjustedIdx); + lViewNode.dynamicParent = null; return this._viewRefs.splice(adjustedIdx, 1)[0] || null; } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 774ecc245d..d687622f35 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -318,7 +318,8 @@ export function createLNodeObject( queries: queries, tNode: null !, pNextOrParent: null, - dynamicLContainerNode: null + dynamicLContainerNode: null, + dynamicParent: null }; } diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index f4c1af3080..9097c82195 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -111,6 +111,12 @@ export interface LNode { */ // TODO(kara): Remove when removing LNodes dynamicLContainerNode: LContainerNode|null; + + /** + * A pointer to a parent LNode created dynamically and virtually by directives requesting + * ViewContainerRef. Applicable only to LContainerNode and LViewNode. + */ + dynamicParent: LElementNode|LContainerNode|LViewNode|null; } @@ -129,6 +135,7 @@ export interface LTextNode extends LNode { native: RText; readonly data: null; dynamicLContainerNode: null; + dynamicParent: null; } /** Abstract node which contains root nodes of a view. */ diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 743a453f5c..962f0724e2 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -42,7 +42,10 @@ export function getParentLNode(node: LContainerNode | LElementNode | LTextNode | export function getParentLNode(node: LViewNode): LContainerNode|null; export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null; export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null { - if (node.tNode.index === -1) return null; + if (node.tNode.index === -1) { + // This is a dynamic container or an embedded view inside a dynamic container. + return node.dynamicParent; + } const parent = node.tNode.parent; return parent ? node.view[parent.index] : node.view[HOST_NODE]; } diff --git a/packages/core/test/render3/common_integration_spec.ts b/packages/core/test/render3/common_integration_spec.ts index c78872fa1d..7111562ce9 100644 --- a/packages/core/test/render3/common_integration_spec.ts +++ b/packages/core/test/render3/common_integration_spec.ts @@ -199,6 +199,83 @@ describe('@angular/common integration', () => { expect(fixture.html) .toEqual(''); }); + + it('should support multiple levels of embedded templates', () => { + /** + * + */ + class MyApp { + items: string[] = ['1', '2']; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + template: (rf: RenderFlags, myApp: MyApp) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'ul'); + { container(1, liTemplate, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngForOf', bind(myApp.items)); + } + + function liTemplate(rf1: RenderFlags, row: NgForOfContext) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'li'); + { container(1, spanTemplate, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + elementProperty(1, 'ngForOf', bind(myApp.items)); + } + } + + function spanTemplate(rf1: RenderFlags, row: NgForOfContext) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { text(1); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + textBinding(1, bind(row.$implicit)); + } + } + }, + directives: () => [NgForOf] + }); + } + + const fixture = new ComponentFixture(MyApp); + + // Change detection cycle, no model changes + fixture.update(); + expect(fixture.html) + .toEqual( + '
  • 12
  • 12
'); + + // Remove the last item + fixture.component.items.length = 1; + fixture.update(); + expect(fixture.html).toEqual('
  • 1
'); + + // Change an item + fixture.component.items[0] = 'one'; + fixture.update(); + expect(fixture.html).toEqual('
  • one
'); + + // Add an item + fixture.component.items.push('two'); + fixture.update(); + expect(fixture.html) + .toEqual( + '
  • onetwo
  • onetwo
'); + }); }); describe('ngIf', () => {