fix(tab-link): avoid potential memory leak (#1877)

* Similarly to #1838, the `tab-link` destroy handler may not be called in certain situations, because it is being inherited from the MdRipple class. This PR explicitly calls the destroy handler.
* Adds a unit test to make sure that the ripples are being cleaned up properly.
This commit is contained in:
Kristiyan Kostadinov
2016-11-28 23:41:51 +01:00
committed by tinayuangao
parent cf1b4b90e9
commit e332f1517c
3 changed files with 74 additions and 4 deletions
+25 -1
View File
@@ -64,7 +64,11 @@ describe('MdRipple', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [MdRippleModule.forRoot()],
declarations: [BasicRippleContainer, RippleContainerWithInputBindings],
declarations: [
BasicRippleContainer,
RippleContainerWithInputBindings,
RippleContainerWithNgIf,
],
});
});
@@ -180,6 +184,20 @@ describe('MdRipple', () => {
expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1);
expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1);
});
it('cleans up the event handlers when the container gets destroyed', () => {
fixture = TestBed.createComponent(RippleContainerWithNgIf);
fixture.detectChanges();
rippleElement = fixture.debugElement.nativeElement.querySelector('[md-ripple]');
rippleBackground = rippleElement.querySelector('.md-ripple-background');
fixture.componentInstance.isDestroyed = true;
fixture.detectChanges();
rippleElement.dispatchEvent(createMouseEvent('mousedown'));
expect(rippleBackground.classList).not.toContain('md-ripple-active');
});
});
describe('configuring behavior', () => {
@@ -367,3 +385,9 @@ class RippleContainerWithInputBindings {
backgroundColor = '';
@ViewChild(MdRipple) ripple: MdRipple;
}
@Component({ template: `<div id="container" md-ripple *ngIf="!isDestroyed"></div>` })
class RippleContainerWithNgIf {
@ViewChild(MdRipple) ripple: MdRipple;
isDestroyed = false;
}
+32 -1
View File
@@ -10,7 +10,8 @@ describe('MdTabNavBar', () => {
TestBed.configureTestingModule({
imports: [MdTabsModule.forRoot()],
declarations: [
SimpleTabNavBarTestApp
SimpleTabNavBarTestApp,
TabLinkWithNgIf,
],
});
@@ -38,6 +39,25 @@ describe('MdTabNavBar', () => {
expect(component.activeIndex).toBe(2);
});
});
it('should clean up the ripple event handlers on destroy', () => {
let fixture: ComponentFixture<TabLinkWithNgIf> = TestBed.createComponent(TabLinkWithNgIf);
fixture.detectChanges();
let link = fixture.debugElement.nativeElement.querySelector('[md-tab-link]');
let rippleBackground = link.querySelector('.md-ripple-background');
let mouseEvent = document.createEvent('MouseEvents');
fixture.componentInstance.isDestroyed = true;
fixture.detectChanges();
mouseEvent.initMouseEvent('mousedown', false, false, window, 0, 0, 0, 0, 0, false, false,
false, false, 0, null);
link.dispatchEvent(mouseEvent);
expect(rippleBackground.classList).not.toContain('md-ripple-active');
});
});
@Component({
@@ -53,3 +73,14 @@ describe('MdTabNavBar', () => {
class SimpleTabNavBarTestApp {
activeIndex = 0;
}
@Component({
template: `
<nav md-tab-nav-bar>
<a md-tab-link *ngIf="!isDestroyed">Link</a>
</nav>
`
})
class TabLinkWithNgIf {
isDestroyed = false;
}
+17 -2
View File
@@ -1,4 +1,13 @@
import {Component, Input, ViewChild, ElementRef, ViewEncapsulation, Directive} from '@angular/core';
import {
Component,
Input,
ViewChild,
ElementRef,
ViewEncapsulation,
Directive,
OnDestroy,
} from '@angular/core';
import {MdInkBar} from '../ink-bar';
import {MdRipple} from '../../core/ripple/ripple';
@@ -50,8 +59,14 @@ export class MdTabLink {
@Directive({
selector: '[md-tab-link]',
})
export class MdTabLinkRipple extends MdRipple {
export class MdTabLinkRipple extends MdRipple implements OnDestroy {
constructor(private _element: ElementRef) {
super(_element);
}
// In certain cases the parent destroy handler
// may not get called. See Angular issue #11606.
ngOnDestroy() {
super.ngOnDestroy();
}
}