Skip to content

Commit

Permalink
fix(core): DebugElement.listeners not cleared on destroy (#31820)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
crisbeto authored and AndrewKushnir committed Jul 29, 2019
1 parent dcbc28f commit 46b160e
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 2 deletions.
6 changes: 5 additions & 1 deletion packages/core/src/view/services.ts
Expand Up @@ -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);
}
Expand Down
37 changes: 36 additions & 1 deletion packages/core/test/debug/debug_node_spec.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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.');
});

});
}

0 comments on commit 46b160e

Please sign in to comment.