diff --git a/src/cdk/scrolling/scroll-dispatcher.spec.ts b/src/cdk/scrolling/scroll-dispatcher.spec.ts index b4c249f4e55..4bca3195cfa 100644 --- a/src/cdk/scrolling/scroll-dispatcher.spec.ts +++ b/src/cdk/scrolling/scroll-dispatcher.spec.ts @@ -180,6 +180,22 @@ describe('Scroll Dispatcher', () => { .toBe(4, 'Expected scrollable count to stay the same'); }); + it('should remove the global subscription on destroy', () => { + expect(scroll._globalSubscription).toBeNull('Expected no global listeners on init.'); + + const subscription = scroll.scrolled(0).subscribe(() => {}); + + expect(scroll._globalSubscription).toBeTruthy( + 'Expected global listeners after a subscription has been added.'); + + scroll.ngOnDestroy(); + + expect(scroll._globalSubscription).toBeNull( + 'Expected global listeners to have been removed after the subscription has stopped.'); + + subscription.unsubscribe(); + }); + }); }); diff --git a/src/cdk/scrolling/scroll-dispatcher.ts b/src/cdk/scrolling/scroll-dispatcher.ts index 5d5c3949777..004ec7adbc0 100644 --- a/src/cdk/scrolling/scroll-dispatcher.ts +++ b/src/cdk/scrolling/scroll-dispatcher.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ElementRef, Injectable, NgZone, Optional, SkipSelf} from '@angular/core'; +import {ElementRef, Injectable, NgZone, Optional, SkipSelf, OnDestroy} from '@angular/core'; import {Platform} from '@angular/cdk/platform'; import {Subject} from 'rxjs/Subject'; import {Subscription} from 'rxjs/Subscription'; @@ -26,7 +26,7 @@ export const DEFAULT_SCROLL_TIME = 20; * Scrollable references emit a scrolled event. */ @Injectable() -export class ScrollDispatcher { +export class ScrollDispatcher implements OnDestroy { constructor(private _ngZone: NgZone, private _platform: Platform) { } /** Subject for notifying that a registered scrollable reference element has been scrolled. */ @@ -97,14 +97,18 @@ export class ScrollDispatcher { subscription.unsubscribe(); this._scrolledCount--; - if (this._globalSubscription && !this._scrolledCount) { - this._globalSubscription.unsubscribe(); - this._globalSubscription = null; + if (!this._scrolledCount) { + this._removeGlobalListener(); } }; }) : observableOf(); } + ngOnDestroy() { + this._removeGlobalListener(); + this.scrollContainers.forEach((_, container) => this.deregister(container)); + } + /** * Returns an observable that emits whenever any of the * scrollable ancestors of an element are scrolled. @@ -146,12 +150,20 @@ export class ScrollDispatcher { return false; } - /** Sets up the global scroll and resize listeners. */ + /** Sets up the global scroll listeners. */ private _addGlobalListener() { this._globalSubscription = this._ngZone.runOutsideAngular(() => { return fromEvent(window.document, 'scroll').subscribe(() => this._scrolled.next()); }); } + + /** Cleans up the global scroll listener. */ + private _removeGlobalListener() { + if (this._globalSubscription) { + this._globalSubscription.unsubscribe(); + this._globalSubscription = null; + } + } } /** @docs-private */