Skip to content

Commit 1b351cd

Browse files
Eddmantinayuangao
authored andcommitted
fix(memory): Unsubscribe event listeners when using Observable.fromEvent (#5325)
* Fixed memory leak in CdkMonitorFocus * Unsubscribe events that are registered under window/document using Observable.fromEvent. * Fixed tslint problems.
1 parent 288f00e commit 1b351cd

File tree

3 files changed

+17
-6
lines changed

3 files changed

+17
-6
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
101101
/** Whether or not the placeholder state is being overridden. */
102102
private _manuallyFloatingPlaceholder = false;
103103

104+
/** The subscription for closing actions (some are bound to document). */
105+
private _closingActionsSubscription: Subscription;
106+
104107
/** View -> model callback called when value changes */
105108
_onChange: (value: any) => void = () => {};
106109

@@ -157,7 +160,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
157160

158161
if (this._overlayRef && !this._overlayRef.hasAttached()) {
159162
this._overlayRef.attach(this._portal);
160-
this._subscribeToClosingActions();
163+
this._closingActionsSubscription = this._subscribeToClosingActions();
161164
}
162165

163166
this.autocomplete._setVisibility();
@@ -169,6 +172,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
169172
closePanel(): void {
170173
if (this._overlayRef && this._overlayRef.hasAttached()) {
171174
this._overlayRef.detach();
175+
this._closingActionsSubscription.unsubscribe();
172176
}
173177

174178
this._panelOpen = false;
@@ -332,9 +336,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
332336
* This method listens to a stream of panel closing actions and resets the
333337
* stream every time the option list changes.
334338
*/
335-
private _subscribeToClosingActions(): void {
339+
private _subscribeToClosingActions(): Subscription {
336340
// When the zone is stable initially, and when the option list changes...
337-
RxChain.from(merge(first.call(this._zone.onStable), this.autocomplete.options.changes))
341+
return RxChain.from(merge(first.call(this._zone.onStable), this.autocomplete.options.changes))
338342
// create a new stream of panelClosingActions, replacing any previous streams
339343
// that were created, and flatten it so our stream only emits closing events...
340344
.call(switchMap, () => {

src/lib/core/style/focus-origin-monitor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from '@angular/core';
2121
import {Observable} from 'rxjs/Observable';
2222
import {Subject} from 'rxjs/Subject';
23+
import {Subscription} from 'rxjs/Subscription';
2324
import {Platform} from '../platform/platform';
2425
import {of as observableOf} from 'rxjs/observable/of';
2526

@@ -316,18 +317,20 @@ export class FocusOriginMonitor {
316317
selector: '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]',
317318
})
318319
export class CdkMonitorFocus implements OnDestroy {
320+
private _monitorSubscription: Subscription;
319321
@Output() cdkFocusChange = new EventEmitter<FocusOrigin>();
320322

321323
constructor(private _elementRef: ElementRef, private _focusOriginMonitor: FocusOriginMonitor,
322324
renderer: Renderer2) {
323-
this._focusOriginMonitor.monitor(
325+
this._monitorSubscription = this._focusOriginMonitor.monitor(
324326
this._elementRef.nativeElement, renderer,
325327
this._elementRef.nativeElement.hasAttribute('cdkMonitorSubtreeFocus'))
326328
.subscribe(origin => this.cdkFocusChange.emit(origin));
327329
}
328330

329331
ngOnDestroy() {
330332
this._focusOriginMonitor.stopMonitoring(this._elementRef.nativeElement);
333+
this._monitorSubscription.unsubscribe();
331334
}
332335
}
333336

src/lib/tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import {CanDisable, mixinDisabled} from '../../core/common-behaviors/disabled';
2525
import {MdRipple} from '../../core';
2626
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
2727
import {Directionality, MD_RIPPLE_GLOBAL_OPTIONS, Platform, RippleGlobalOptions} from '../../core';
28-
import {Observable} from 'rxjs/Observable';
2928
import {Subject} from 'rxjs/Subject';
29+
import {Subscription} from 'rxjs/Subscription';
3030
import {takeUntil, auditTime} from '../../core/rxjs/index';
3131
import {of as observableOf} from 'rxjs/observable/of';
3232
import {merge} from 'rxjs/observable/merge';
@@ -53,6 +53,9 @@ export class MdTabNav implements AfterContentInit, OnDestroy {
5353

5454
@ViewChild(MdInkBar) _inkBar: MdInkBar;
5555

56+
/** Subscription for window.resize event **/
57+
private _resizeSubscription: Subscription;
58+
5659
constructor(@Optional() private _dir: Directionality, private _ngZone: NgZone) { }
5760

5861
/** Notifies the component that the active link has been changed. */
@@ -62,7 +65,7 @@ export class MdTabNav implements AfterContentInit, OnDestroy {
6265
}
6366

6467
ngAfterContentInit(): void {
65-
this._ngZone.runOutsideAngular(() => {
68+
this._resizeSubscription = this._ngZone.runOutsideAngular(() => {
6669
let dirChange = this._dir ? this._dir.change : observableOf(null);
6770
let resize = typeof window !== 'undefined' ?
6871
auditTime.call(fromEvent(window, 'resize'), 10) :
@@ -83,6 +86,7 @@ export class MdTabNav implements AfterContentInit, OnDestroy {
8386

8487
ngOnDestroy() {
8588
this._onDestroy.next();
89+
this._resizeSubscription.unsubscribe();
8690
}
8791

8892
/** Aligns the ink bar to the active link. */

0 commit comments

Comments
 (0)