fix(ivy): properly destroy views created by ComponentFactory (#27676)

PR Close #27676
This commit is contained in:
Pawel Kozlowski 2018-12-14 15:11:14 +01:00 committed by Miško Hevery
parent f1c9d6a81f
commit b00aeeff37
5 changed files with 83 additions and 89 deletions

View File

@ -22,11 +22,10 @@ import {assertComponentType, assertDefined} from './assert';
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
import {getComponentDef} from './definition';
import {NodeInjector} from './di';
import {createLView, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions';
import {addToViewTree, createLView, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions';
import {ComponentDef, RenderFlags} from './interfaces/definition';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
import {RElement, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {HEADER_OFFSET, LView, LViewFlags, RootContext, TVIEW} from './interfaces/view';
import {enterView, leaveView} from './state';
import {defaultScheduler, getTNode} from './util';
@ -169,6 +168,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
const componentView = createRootComponentView(
hostRNode, this.componentDef, rootLView, rendererFactory, renderer);
tElementNode = getTNode(0, rootLView) as TElementNode;
// Transform the arrays of native nodes into a structure that can be consumed by the
@ -207,6 +207,8 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
component = createRootComponent(
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
addToViewTree(rootLView, HEADER_OFFSET, componentView);
refreshDescendantViews(rootLView, RenderFlags.Create);
} finally {
leaveView(oldLView, true);

View File

@ -455,16 +455,18 @@ export function getParentState(state: LView | LContainer, rootView: LView): LVie
}
/**
* Removes all listeners and call all onDestroys in a given view.
* Calls onDestroys hooks for all directives and pipes in a given view and then removes all
* listeners. Listeners are removed as the last step so events delivered in the onDestroys hooks
* can be propagated to @Output listeners.
*
* @param view The LView to clean up
*/
function cleanUpView(viewOrContainer: LView | LContainer): void {
if ((viewOrContainer as LView).length >= HEADER_OFFSET) {
const view = viewOrContainer as LView;
removeListeners(view);
executeOnDestroys(view);
executePipeOnDestroys(view);
removeListeners(view);
const hostTNode = view[HOST_NODE];
// For component views only, the local renderer is destroyed as clean up time.
if (hostTNode && hostTNode.type === TNodeType.Element && isProceduralRenderer(view[RENDERER])) {

View File

@ -1104,8 +1104,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
});
describe('ngOnDestroy', () => {
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
.it('should be called on view destruction', fakeAsync(() => {
it('should be called on view destruction', fakeAsync(() => {
const ctx = createCompFixture('<div testDirective="dir"></div>');
ctx.detectChanges(false);
@ -1114,13 +1113,10 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
expect(directiveLog.filter(['ngOnDestroy'])).toEqual(['dir.ngOnDestroy']);
}));
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
.it('should be called after processing the content and view children', fakeAsync(() => {
it('should be called after processing the content and view children', fakeAsync(() => {
TestBed.overrideComponent(AnotherComponent, {
set: new Component({
selector: 'other-cmp',
template: '<div testDirective="viewChild"></div>'
})
set: new Component(
{selector: 'other-cmp', template: '<div testDirective="viewChild"></div>'})
});
const ctx = createCompFixture(
@ -1132,13 +1128,12 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
ctx.destroy();
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
'contentChild0.ngOnDestroy', 'contentChild1.ngOnDestroy',
'viewChild.ngOnDestroy', 'parent.ngOnDestroy'
'contentChild0.ngOnDestroy', 'contentChild1.ngOnDestroy', 'viewChild.ngOnDestroy',
'parent.ngOnDestroy'
]);
}));
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
.it('should be called in reverse order so the child is always notified before the parent',
it('should be called in reverse order so the child is always notified before the parent',
fakeAsync(() => {
const ctx = createCompFixture(
'<div testDirective="parent"><div testDirective="child"></div></div><div testDirective="sibling"></div>');
@ -1151,10 +1146,8 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
]);
}));
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
.it('should deliver synchronous events to parent', fakeAsync(() => {
const ctx =
createCompFixture('<div (destroy)="a=$event" onDestroyDirective></div>');
it('should deliver synchronous events to parent', fakeAsync(() => {
const ctx = createCompFixture('<div (destroy)="a=$event" onDestroyDirective></div>');
ctx.detectChanges(false);
ctx.destroy();
@ -1162,8 +1155,8 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
expect(ctx.componentInstance.a).toEqual('destroyed');
}));
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
.it('should call ngOnDestroy on pipes', fakeAsync(() => {
it('should call ngOnDestroy on pipes', fakeAsync(() => {
const ctx = createCompFixture('{{true | pipeWithOnDestroy }}');
ctx.detectChanges(false);
@ -1174,7 +1167,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
]);
}));
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
fixmeIvy('FW-848: ngOnDestroy hooks are not called on providers')
.it('should call ngOnDestroy on an injectable class', fakeAsync(() => {
TestBed.overrideDirective(
TestDirective, {set: {providers: [InjectableWithLifecycle]}});

View File

@ -768,9 +768,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective);
});
fixmeIvy(
'FW-763: LView tree not properly constructed / destroyed for dynamically inserted components')
.it('should support events via EventEmitter on regular elements', async(() => {
it('should support events via EventEmitter on regular elements', async(() => {
TestBed.configureTestingModule(
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]});
const template = '<div emitter listener></div>';

View File

@ -616,9 +616,8 @@ describe('Query API', () => {
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
});
fixmeIvy(
'FW-763 - LView tree not properly constructed / destroyed for dynamically inserted components')
.it('should remove manually projected templates if their parent view is destroyed', () => {
fixmeIvy('unknown').it(
'should remove manually projected templates if their parent view is destroyed', () => {
const template = `
<manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting>
<div *ngIf="shouldShow">