-
Notifications
You must be signed in to change notification settings - Fork 6.8k
perf(scroll-dispatcher): lazily subscribe to global events #3270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
fae1529
f7959bd
be04363
fc105e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import {Subject} from 'rxjs/Subject'; | |
import {Observable} from 'rxjs/Observable'; | ||
import {Subscription} from 'rxjs/Subscription'; | ||
import 'rxjs/add/observable/fromEvent'; | ||
import 'rxjs/add/observable/merge'; | ||
import 'rxjs/add/operator/auditTime'; | ||
|
||
|
||
|
@@ -19,26 +20,28 @@ export class ScrollDispatcher { | |
/** Subject for notifying that a registered scrollable reference element has been scrolled. */ | ||
_scrolled: Subject<void> = new Subject<void>(); | ||
|
||
/** Keeps track of the global `scroll` and `resize` subscriptions. */ | ||
_globalSubscription: Subscription = null; | ||
|
||
/** Keeps track of the amount of subscriptions to `scrolled`. Used for cleaning up afterwards. */ | ||
private _scrolledCount = 0; | ||
|
||
/** | ||
* Map of all the scrollable references that are registered with the service and their | ||
* scroll event subscriptions. | ||
*/ | ||
scrollableReferences: Map<Scrollable, Subscription> = new Map(); | ||
|
||
constructor() { | ||
// By default, notify a scroll event when the document is scrolled or the window is resized. | ||
Observable.fromEvent(window.document, 'scroll').subscribe(() => this._notify()); | ||
Observable.fromEvent(window, 'resize').subscribe(() => this._notify()); | ||
} | ||
|
||
/** | ||
* Registers a Scrollable with the service and listens for its scrolled events. When the | ||
* scrollable is scrolled, the service emits the event in its scrolled observable. | ||
* @param scrollable Scrollable instance to be registered. | ||
*/ | ||
register(scrollable: Scrollable): void { | ||
const scrollSubscription = scrollable.elementScrolled().subscribe(() => this._notify()); | ||
|
||
this.scrollableReferences.set(scrollable, scrollSubscription); | ||
this._addGlobalSubscription(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we still be adding/removing the global subscription in register and deregister? I thought that we would only worry about them through the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I think I was working under the impression that this was used both for the scrollables and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's changed now. |
||
} | ||
|
||
/** | ||
|
@@ -49,22 +52,31 @@ export class ScrollDispatcher { | |
if (this.scrollableReferences.has(scrollable)) { | ||
this.scrollableReferences.get(scrollable).unsubscribe(); | ||
this.scrollableReferences.delete(scrollable); | ||
this._removeGlobalSubscription(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns an observable that emits an event whenever any of the registered Scrollable | ||
* Subscribes to an observable that emits an event whenever any of the registered Scrollable | ||
* references (or window, document, or body) fire a scrolled event. Can provide a time in ms | ||
* to override the default "throttle" time. | ||
*/ | ||
scrolled(auditTimeInMs: number = DEFAULT_SCROLL_TIME): Observable<void> { | ||
// In the case of a 0ms delay, return the observable without auditTime since it does add | ||
// a perceptible delay in processing overhead. | ||
if (auditTimeInMs == 0) { | ||
return this._scrolled.asObservable(); | ||
} | ||
|
||
return this._scrolled.asObservable().auditTime(auditTimeInMs); | ||
scrolled(auditTimeInMs: number = DEFAULT_SCROLL_TIME, callback: () => any): Subscription { | ||
// In the case of a 0ms delay, use an observable without auditTime | ||
// since it does add a perceptible delay in processing overhead. | ||
let observable = auditTimeInMs > 0 ? | ||
this._scrolled.asObservable().auditTime(auditTimeInMs) : | ||
this._scrolled.asObservable(); | ||
|
||
this._scrolledCount++; | ||
this._addGlobalSubscription(); | ||
|
||
// Note that we need to do the subscribing from here, in order to be able to remove | ||
// the global event listeners once there are no more subscriptions. | ||
return observable.subscribe(callback).add(() => { | ||
this._scrolledCount--; | ||
this._removeGlobalSubscription(); | ||
}); | ||
} | ||
|
||
/** Returns all registered Scrollables that contain the provided element. */ | ||
|
@@ -96,6 +108,24 @@ export class ScrollDispatcher { | |
_notify() { | ||
this._scrolled.next(); | ||
} | ||
|
||
/** Sets up the global event listeners, if they're not active already. */ | ||
private _addGlobalSubscription(): void { | ||
if (!this._globalSubscription) { | ||
this._globalSubscription = Observable.merge( | ||
Observable.fromEvent(window.document, 'scroll'), | ||
Observable.fromEvent(window, 'resize') | ||
).subscribe(() => this._notify()); | ||
} | ||
} | ||
|
||
/** Removes the global event listeners, if there are no more subscriptions listening to them. */ | ||
private _removeGlobalSubscription(): void { | ||
if (this._globalSubscription && !this.scrollableReferences.size && !this._scrolledCount) { | ||
this._globalSubscription.unsubscribe(); | ||
this._globalSubscription = null; | ||
} | ||
} | ||
} | ||
|
||
export function SCROLL_DISPATCHER_PROVIDER_FACTORY(parentDispatcher: ScrollDispatcher) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment about when this should have subscriptions (when there is at least one actively registered scrollable).