From 2bb518c694b1ebe082ee31220edb3caa7bfc7882 Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Mon, 28 Jan 2019 14:52:37 -0800 Subject: [PATCH] fix(ivy): add root components to the root view tree in renderComponent (#28409) Previously, these components were not added to the view tree for the (fake) root view in which they were bootstrapped. Without this, root view destruction does not work as expected since the root view's children are not present to be also destroyed. PR Close #28409 --- packages/core/src/render3/component.ts | 4 +- .../hello_world/bundle.golden_symbols.json | 12 ++++++ packages/core/test/render3/component_spec.ts | 38 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) 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'); + }); +});