perf: memory leak when subscribing to zone events (#6918)

Fixes a few memory leaks that were caused by subscribing to `NgZone.onMicrotaskEmpty` or `NgZone.onStable`. Because the two streams are `EventEmitters`, the subscription doesn't get GC-ed correctly. These changes switch to converting the emitters to observables before subscribing to them.

Fixes #6905.
This commit is contained in:
Kristiyan Kostadinov
2017-09-12 02:33:56 +02:00
committed by mmalerba
parent a1ec81ab69
commit f6c9172afe
7 changed files with 19 additions and 16 deletions
+1 -1
View File
@@ -265,7 +265,7 @@ export class FocusTrap {
if (this._ngZone.isStable) {
fn();
} else {
first.call(this._ngZone.onStable).subscribe(fn);
first.call(this._ngZone.onStable.asObservable()).subscribe(fn);
}
}
}
+1 -1
View File
@@ -372,7 +372,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
* stream every time the option list changes.
*/
private _subscribeToClosingActions(): Subscription {
const firstStable = first.call(this._zone.onStable);
const firstStable = first.call(this._zone.onStable.asObservable());
const optionChanges = map.call(this.autocomplete.options.changes, () =>
this._positionStrategy.recalculateLastPosition());
+5 -3
View File
@@ -218,9 +218,11 @@ export class MdCalendar<D> implements AfterContentInit, OnDestroy {
/** Focuses the active cell after the microtask queue is empty. */
_focusActiveCell() {
this._ngZone.runOutsideAngular(() => first.call(this._ngZone.onStable).subscribe(() => {
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active').focus();
}));
this._ngZone.runOutsideAngular(() => {
first.call(this._ngZone.onStable.asObservable()).subscribe(() => {
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active').focus();
});
});
}
/** Whether the two dates represent the same view in the current view mode (month or year). */
+3 -1
View File
@@ -323,7 +323,9 @@ export class MdDatepicker<D> implements OnDestroy {
componentRef.instance.datepicker = this;
// Update the position once the calendar has rendered.
first.call(this._ngZone.onStable).subscribe(() => this._popupRef.updatePosition());
first.call(this._ngZone.onStable.asObservable()).subscribe(() => {
this._popupRef.updatePosition();
});
}
this._popupRef.backdropClick().subscribe(() => this.close());
+5 -2
View File
@@ -409,8 +409,11 @@ export class MdDrawerContainer implements AfterContentInit, OnDestroy {
}
// NOTE: We need to wait for the microtask queue to be empty before validating,
// since both drawers may be swapping positions at the same time.
takeUntil.call(drawer.onPositionChanged, this._drawers.changes).subscribe(() =>
first.call(this._ngZone.onMicrotaskEmpty).subscribe(() => this._validateDrawers()));
takeUntil.call(drawer.onPositionChanged, this._drawers.changes).subscribe(() => {
first.call(this._ngZone.onMicrotaskEmpty.asObservable()).subscribe(() => {
this._validateDrawers();
});
});
}
/** Toggles the 'mat-drawer-opened' class on the main 'md-drawer-container' element. */
+3 -7
View File
@@ -186,13 +186,9 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
* errors where we end up removing an element which is in the middle of an animation.
*/
private _completeExit() {
// Note: we shouldn't use `this` inside the zone callback,
// because it can cause a memory leak.
const onExit = this._onExit;
first.call(this._ngZone.onMicrotaskEmpty).subscribe(() => {
onExit.next();
onExit.complete();
first.call(this._ngZone.onMicrotaskEmpty.asObservable()).subscribe(() => {
this._onExit.next();
this._onExit.complete();
});
}
}
+1 -1
View File
@@ -392,7 +392,7 @@ export class MdTooltip implements OnDestroy {
this._tooltipInstance.message = this.message;
this._tooltipInstance._markForCheck();
first.call(this._ngZone.onMicrotaskEmpty).subscribe(() => {
first.call(this._ngZone.onMicrotaskEmpty.asObservable()).subscribe(() => {
if (this._tooltipInstance) {
this._overlayRef!.updatePosition();
}