fix(core): DebugElement.listeners not cleared on destroy (#31820)

Currently the `DebugElement.listeners` array are retained after the node is destroyed. This means that they'll continue to fire through `triggerEventHandler` and can cause memory leaks. This has already been fixed in Ivy, but these changes fix it in ViewEngine for consistency.

PR Close #31820
This commit is contained in:
Kristiyan Kostadinov 2019-07-24 16:57:55 +02:00 committed by Andrew Kushnir
parent 5f0d5e9ccf
commit 14dba72aee
2 changed files with 41 additions and 2 deletions

View File

@ -688,7 +688,11 @@ export class DebugRenderer2 implements Renderer2 {
constructor(private delegate: Renderer2) { this.data = this.delegate.data; }
destroyNode(node: any) {
removeDebugNodeFromIndex(getDebugNode(node) !);
const debugNode = getDebugNode(node) !;
removeDebugNodeFromIndex(debugNode);
if (debugNode instanceof DebugNode__PRE_R3__) {
debugNode.listeners.length = 0;
}
if (this.delegate.destroyNode) {
this.delegate.destroyNode(node);
}

View File

@ -8,7 +8,7 @@
import {CommonModule, NgIfContext} from '@angular/common';
import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -927,5 +927,40 @@ class TestCmptWithPropBindings {
expect(div.attributes.foo).toBe('bar');
});
it('should clear event listeners when node is destroyed', () => {
let calls = 0;
@Component({
selector: 'cancel-button',
template: '',
})
class CancelButton {
@Output() cancel = new EventEmitter<void>();
}
@Component({
template: '<cancel-button *ngIf="visible" (cancel)="cancel()"></cancel-button>',
})
class App {
visible = true;
cancel() { calls++; }
}
TestBed.configureTestingModule({declarations: [App, CancelButton]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const button = fixture.debugElement.query(By.directive(CancelButton));
button.triggerEventHandler('cancel', {});
expect(calls).toBe(1, 'Expected calls to be 1 after one event.');
fixture.componentInstance.visible = false;
fixture.detectChanges();
button.triggerEventHandler('cancel', {});
expect(calls).toBe(1, 'Expected calls to stay 1 after destroying the node.');
});
});
}