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
This commit is contained in:
Jeremy Elbourn 2019-01-28 14:52:37 -08:00 committed by Jason Aden
parent b87bf39eb4
commit 2bb518c694
3 changed files with 53 additions and 1 deletions

View File

@ -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<T>(
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

View File

@ -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"
},

View File

@ -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');
});
});