Skip to content

Commit b00aeef

Browse files
pkozlowski-opensourcemhevery
authored andcommitted
fix(ivy): properly destroy views created by ComponentFactory (angular#27676)
PR Close angular#27676
1 parent f1c9d6a commit b00aeef

File tree

5 files changed

+85
-91
lines changed

5 files changed

+85
-91
lines changed

packages/core/src/render3/component_ref.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ import {assertComponentType, assertDefined} from './assert';
2222
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
2323
import {getComponentDef} from './definition';
2424
import {NodeInjector} from './di';
25-
import {createLView, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions';
25+
import {addToViewTree, createLView, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions';
2626
import {ComponentDef, RenderFlags} from './interfaces/definition';
2727
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
2828
import {RElement, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
29-
import {SanitizerFn} from './interfaces/sanitization';
3029
import {HEADER_OFFSET, LView, LViewFlags, RootContext, TVIEW} from './interfaces/view';
3130
import {enterView, leaveView} from './state';
3231
import {defaultScheduler, getTNode} from './util';
@@ -169,6 +168,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
169168

170169
const componentView = createRootComponentView(
171170
hostRNode, this.componentDef, rootLView, rendererFactory, renderer);
171+
172172
tElementNode = getTNode(0, rootLView) as TElementNode;
173173

174174
// 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> {
207207
component = createRootComponent(
208208
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
209209

210+
addToViewTree(rootLView, HEADER_OFFSET, componentView);
211+
210212
refreshDescendantViews(rootLView, RenderFlags.Create);
211213
} finally {
212214
leaveView(oldLView, true);

packages/core/src/render3/node_manipulation.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,16 +455,18 @@ export function getParentState(state: LView | LContainer, rootView: LView): LVie
455455
}
456456

457457
/**
458-
* Removes all listeners and call all onDestroys in a given view.
458+
* Calls onDestroys hooks for all directives and pipes in a given view and then removes all
459+
* listeners. Listeners are removed as the last step so events delivered in the onDestroys hooks
460+
* can be propagated to @Output listeners.
459461
*
460462
* @param view The LView to clean up
461463
*/
462464
function cleanUpView(viewOrContainer: LView | LContainer): void {
463465
if ((viewOrContainer as LView).length >= HEADER_OFFSET) {
464466
const view = viewOrContainer as LView;
465-
removeListeners(view);
466467
executeOnDestroys(view);
467468
executePipeOnDestroys(view);
469+
removeListeners(view);
468470
const hostTNode = view[HOST_NODE];
469471
// For component views only, the local renderer is destroyed as clean up time.
470472
if (hostTNode && hostTNode.type === TNodeType.Element && isProceduralRenderer(view[RENDERER])) {

packages/core/test/linker/change_detection_integration_spec.ts

Lines changed: 48 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,77 +1104,70 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
11041104
});
11051105

11061106
describe('ngOnDestroy', () => {
1107-
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
1108-
.it('should be called on view destruction', fakeAsync(() => {
1109-
const ctx = createCompFixture('<div testDirective="dir"></div>');
1110-
ctx.detectChanges(false);
1107+
it('should be called on view destruction', fakeAsync(() => {
1108+
const ctx = createCompFixture('<div testDirective="dir"></div>');
1109+
ctx.detectChanges(false);
11111110

1112-
ctx.destroy();
1111+
ctx.destroy();
11131112

1114-
expect(directiveLog.filter(['ngOnDestroy'])).toEqual(['dir.ngOnDestroy']);
1115-
}));
1113+
expect(directiveLog.filter(['ngOnDestroy'])).toEqual(['dir.ngOnDestroy']);
1114+
}));
11161115

1117-
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
1118-
.it('should be called after processing the content and view children', fakeAsync(() => {
1119-
TestBed.overrideComponent(AnotherComponent, {
1120-
set: new Component({
1121-
selector: 'other-cmp',
1122-
template: '<div testDirective="viewChild"></div>'
1123-
})
1124-
});
1116+
it('should be called after processing the content and view children', fakeAsync(() => {
1117+
TestBed.overrideComponent(AnotherComponent, {
1118+
set: new Component(
1119+
{selector: 'other-cmp', template: '<div testDirective="viewChild"></div>'})
1120+
});
11251121

1126-
const ctx = createCompFixture(
1127-
'<div testDirective="parent"><div *ngFor="let x of [0,1]" testDirective="contentChild{{x}}"></div>' +
1128-
'<other-cmp></other-cmp></div>',
1129-
TestComponent);
1122+
const ctx = createCompFixture(
1123+
'<div testDirective="parent"><div *ngFor="let x of [0,1]" testDirective="contentChild{{x}}"></div>' +
1124+
'<other-cmp></other-cmp></div>',
1125+
TestComponent);
11301126

1131-
ctx.detectChanges(false);
1132-
ctx.destroy();
1127+
ctx.detectChanges(false);
1128+
ctx.destroy();
11331129

1134-
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
1135-
'contentChild0.ngOnDestroy', 'contentChild1.ngOnDestroy',
1136-
'viewChild.ngOnDestroy', 'parent.ngOnDestroy'
1137-
]);
1138-
}));
1130+
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
1131+
'contentChild0.ngOnDestroy', 'contentChild1.ngOnDestroy', 'viewChild.ngOnDestroy',
1132+
'parent.ngOnDestroy'
1133+
]);
1134+
}));
11391135

1140-
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
1141-
.it('should be called in reverse order so the child is always notified before the parent',
1142-
fakeAsync(() => {
1143-
const ctx = createCompFixture(
1144-
'<div testDirective="parent"><div testDirective="child"></div></div><div testDirective="sibling"></div>');
1136+
it('should be called in reverse order so the child is always notified before the parent',
1137+
fakeAsync(() => {
1138+
const ctx = createCompFixture(
1139+
'<div testDirective="parent"><div testDirective="child"></div></div><div testDirective="sibling"></div>');
11451140

1146-
ctx.detectChanges(false);
1147-
ctx.destroy();
1141+
ctx.detectChanges(false);
1142+
ctx.destroy();
11481143

1149-
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
1150-
'child.ngOnDestroy', 'parent.ngOnDestroy', 'sibling.ngOnDestroy'
1151-
]);
1152-
}));
1144+
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
1145+
'child.ngOnDestroy', 'parent.ngOnDestroy', 'sibling.ngOnDestroy'
1146+
]);
1147+
}));
11531148

1154-
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
1155-
.it('should deliver synchronous events to parent', fakeAsync(() => {
1156-
const ctx =
1157-
createCompFixture('<div (destroy)="a=$event" onDestroyDirective></div>');
1149+
it('should deliver synchronous events to parent', fakeAsync(() => {
1150+
const ctx = createCompFixture('<div (destroy)="a=$event" onDestroyDirective></div>');
11581151

1159-
ctx.detectChanges(false);
1160-
ctx.destroy();
1152+
ctx.detectChanges(false);
1153+
ctx.destroy();
11611154

1162-
expect(ctx.componentInstance.a).toEqual('destroyed');
1163-
}));
1155+
expect(ctx.componentInstance.a).toEqual('destroyed');
1156+
}));
11641157

1165-
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
1166-
.it('should call ngOnDestroy on pipes', fakeAsync(() => {
1167-
const ctx = createCompFixture('{{true | pipeWithOnDestroy }}');
11681158

1169-
ctx.detectChanges(false);
1170-
ctx.destroy();
1159+
it('should call ngOnDestroy on pipes', fakeAsync(() => {
1160+
const ctx = createCompFixture('{{true | pipeWithOnDestroy }}');
11711161

1172-
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
1173-
'pipeWithOnDestroy.ngOnDestroy'
1174-
]);
1175-
}));
1162+
ctx.detectChanges(false);
1163+
ctx.destroy();
1164+
1165+
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
1166+
'pipeWithOnDestroy.ngOnDestroy'
1167+
]);
1168+
}));
11761169

1177-
fixmeIvy('FW-763: LView tree not properly constructed / destroyed')
1170+
fixmeIvy('FW-848: ngOnDestroy hooks are not called on providers')
11781171
.it('should call ngOnDestroy on an injectable class', fakeAsync(() => {
11791172
TestBed.overrideDirective(
11801173
TestDirective, {set: {providers: [InjectableWithLifecycle]}});

packages/core/test/linker/integration_spec.ts

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -768,37 +768,35 @@ function declareTests(config?: {useJit: boolean}) {
768768
expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective);
769769
});
770770

771-
fixmeIvy(
772-
'FW-763: LView tree not properly constructed / destroyed for dynamically inserted components')
773-
.it('should support events via EventEmitter on regular elements', async(() => {
774-
TestBed.configureTestingModule(
775-
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]});
776-
const template = '<div emitter listener></div>';
777-
TestBed.overrideComponent(MyComp, {set: {template}});
778-
const fixture = TestBed.createComponent(MyComp);
779-
780-
const tc = fixture.debugElement.children[0];
781-
const emitter = tc.injector.get(DirectiveEmittingEvent);
782-
const listener = tc.injector.get(DirectiveListeningEvent);
783-
784-
expect(listener.msg).toEqual('');
785-
let eventCount = 0;
771+
it('should support events via EventEmitter on regular elements', async(() => {
772+
TestBed.configureTestingModule(
773+
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]});
774+
const template = '<div emitter listener></div>';
775+
TestBed.overrideComponent(MyComp, {set: {template}});
776+
const fixture = TestBed.createComponent(MyComp);
786777

787-
emitter.event.subscribe({
788-
next: () => {
789-
eventCount++;
790-
if (eventCount === 1) {
791-
expect(listener.msg).toEqual('fired !');
792-
fixture.destroy();
793-
emitter.fireEvent('fired again !');
794-
} else {
795-
expect(listener.msg).toEqual('fired !');
796-
}
797-
}
798-
});
778+
const tc = fixture.debugElement.children[0];
779+
const emitter = tc.injector.get(DirectiveEmittingEvent);
780+
const listener = tc.injector.get(DirectiveListeningEvent);
781+
782+
expect(listener.msg).toEqual('');
783+
let eventCount = 0;
784+
785+
emitter.event.subscribe({
786+
next: () => {
787+
eventCount++;
788+
if (eventCount === 1) {
789+
expect(listener.msg).toEqual('fired !');
790+
fixture.destroy();
791+
emitter.fireEvent('fired again !');
792+
} else {
793+
expect(listener.msg).toEqual('fired !');
794+
}
795+
}
796+
});
799797

800-
emitter.fireEvent('fired !');
801-
}));
798+
emitter.fireEvent('fired !');
799+
}));
802800

803801
fixmeIvy(
804802
'FW-665: Discovery util fails with Unable to find the given context data for the given target')

packages/core/test/linker/query_integration_spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -616,9 +616,8 @@ describe('Query API', () => {
616616
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
617617
});
618618

619-
fixmeIvy(
620-
'FW-763 - LView tree not properly constructed / destroyed for dynamically inserted components')
621-
.it('should remove manually projected templates if their parent view is destroyed', () => {
619+
fixmeIvy('unknown').it(
620+
'should remove manually projected templates if their parent view is destroyed', () => {
622621
const template = `
623622
<manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting>
624623
<div *ngIf="shouldShow">

0 commit comments

Comments
 (0)