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:
parent
b87bf39eb4
commit
2bb518c694
|
@ -18,7 +18,7 @@ import {getComponentDef} from './definition';
|
||||||
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
|
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
|
||||||
import {publishDefaultGlobalUtils} from './global_utils';
|
import {publishDefaultGlobalUtils} from './global_utils';
|
||||||
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
|
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 {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
|
||||||
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||||
import {PlayerHandler} from './interfaces/player';
|
import {PlayerHandler} from './interfaces/player';
|
||||||
|
@ -134,6 +134,8 @@ export function renderComponent<T>(
|
||||||
component = createRootComponent(
|
component = createRootComponent(
|
||||||
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
|
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
|
||||||
|
|
||||||
|
addToViewTree(rootView, HEADER_OFFSET, componentView);
|
||||||
|
|
||||||
refreshDescendantViews(rootView); // creation mode pass
|
refreshDescendantViews(rootView); // creation mode pass
|
||||||
rootView[FLAGS] &= ~LViewFlags.CreationMode;
|
rootView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||||
refreshDescendantViews(rootView); // update mode pass
|
refreshDescendantViews(rootView); // update mode pass
|
||||||
|
|
|
@ -101,6 +101,9 @@
|
||||||
{
|
{
|
||||||
"name": "SANITIZER"
|
"name": "SANITIZER"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "TAIL"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "TVIEW"
|
"name": "TVIEW"
|
||||||
},
|
},
|
||||||
|
@ -128,6 +131,9 @@
|
||||||
{
|
{
|
||||||
"name": "_renderCompCount"
|
"name": "_renderCompCount"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "addToViewTree"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "appendChild"
|
"name": "appendChild"
|
||||||
},
|
},
|
||||||
|
@ -209,6 +215,9 @@
|
||||||
{
|
{
|
||||||
"name": "extractPipeDef"
|
"name": "extractPipeDef"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "firstTemplatePass"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "generateExpandoInstructionBlock"
|
"name": "generateExpandoInstructionBlock"
|
||||||
},
|
},
|
||||||
|
@ -233,6 +242,9 @@
|
||||||
{
|
{
|
||||||
"name": "getDirectiveDef"
|
"name": "getDirectiveDef"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getFirstTemplatePass"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getHighestElementOrICUContainer"
|
"name": "getHighestElementOrICUContainer"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue