diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 0bc8cbaff3..927d8a5104 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -18,7 +18,7 @@ import {getComponentDef} from './definition'; import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di'; import {publishDefaultGlobalUtils} from './global_utils'; import {registerPostOrderHooks, registerPreOrderHooks} from './hooks'; -import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions'; +import {addToViewTree, CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews,} from './instructions'; import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition'; import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {PlayerHandler} from './interfaces/player'; @@ -134,6 +134,8 @@ export function renderComponent( component = createRootComponent( componentView, componentDef, rootView, rootContext, opts.hostFeatures || null); + addToViewTree(rootView, HEADER_OFFSET, componentView); + refreshDescendantViews(rootView); // creation mode pass rootView[FLAGS] &= ~LViewFlags.CreationMode; refreshDescendantViews(rootView); // update mode pass diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 2cb69b4040..5d9e4d566b 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -101,6 +101,9 @@ { "name": "SANITIZER" }, + { + "name": "TAIL" + }, { "name": "TVIEW" }, @@ -128,6 +131,9 @@ { "name": "_renderCompCount" }, + { + "name": "addToViewTree" + }, { "name": "appendChild" }, @@ -209,6 +215,9 @@ { "name": "extractPipeDef" }, + { + "name": "firstTemplatePass" + }, { "name": "generateExpandoInstructionBlock" }, @@ -233,6 +242,9 @@ { "name": "getDirectiveDef" }, + { + "name": "getFirstTemplatePass" + }, { "name": "getHighestElementOrICUContainer" }, diff --git a/packages/core/test/render3/component_spec.ts b/packages/core/test/render3/component_spec.ts index 743590a276..c9bd12b092 100644 --- a/packages/core/test/render3/component_spec.ts +++ b/packages/core/test/render3/component_spec.ts @@ -666,3 +666,41 @@ describe('recursive components', () => { }); }); + +describe('view destruction', () => { + let wasOnDestroyCalled = false; + + class ComponentWithOnDestroy { + static ngComponentDef = defineComponent({ + selectors: [['comp-with-destroy']], + type: ComponentWithOnDestroy, + consts: 0, + vars: 0, + factory: () => new ComponentWithOnDestroy(), + template: (rf: any, ctx: any) => {}, + }); + + ngOnDestroy() { wasOnDestroyCalled = true; } + } + + it('should invoke onDestroy when directly destroying a root view', () => { + // This test asserts that the view tree is set up correctly based on the knowledge that this + // tree is used during view destruction. If the child view is not correctly attached as a + // child of the root view, then the onDestroy hook on the child view will never be called + // when the view tree is torn down following the destruction of that root view. + const ComponentWithChildOnDestroy = createComponent('test-app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'comp-with-destroy'); + } + }, 1, 0, [ComponentWithOnDestroy], [], null, [], []); + + const fixture = new ComponentFixture(ComponentWithChildOnDestroy); + fixture.update(); + + fixture.destroy(); + expect(wasOnDestroyCalled) + .toBe( + true, + 'Expected component onDestroy method to be called when its parent view is destroyed'); + }); +});