-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
feat(scroll): provide directive and service to listen to scrolling #2188
Conversation
* Scrollable references emit a scrolled event. | ||
*/ | ||
@Injectable() | ||
export class Scroll { |
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.
I would call this ScrollMonitor
or ScrollDispatcher
(I'm partial to the latter)
* Map of all the scrollable references that are registered with the service and their | ||
* scroll event subscriptions. | ||
*/ | ||
scrollableReferences: Map<Scrollable, Subscription> = new Map(); |
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.
Use a WeakMap
constructor() { | ||
// By default, notify a scroll event when the document is scrolled or the window is resized. | ||
window.document.addEventListener('scroll', this._notify.bind(this)); | ||
window.addEventListener('resize', this._notify.bind(this)); |
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.
Prefer arrow functions to .bind
:
window.document.addEventListener('scroll', () => this.notify());
(here and elsewhere)
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.
this.renderer.listenGlobal('window', 'resize', (e: Event) => {})
@Injectable() | ||
export class Scroll { | ||
/** Subject for notifying that a registered scrollable reference element has been scrolled. */ | ||
_scrolled: Subject<Event> = new Subject(); |
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.
Do you need the Event
, or can this be void
?
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.
Right now I don't have a need for it. I can remove the event until it is required.
* to through the service. | ||
*/ | ||
@Directive({ | ||
selector: '[md-scrollable]' |
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.
Let's make this cdk-scrollable
(it's starting!)
exports: [Scrollable], | ||
declarations: [Scrollable], | ||
}) | ||
export class ScrollModule { |
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.
I'm not sure this needs its own module vs. just being a part of OverlayModule
. Thoughts?
// Emit a scroll event from the scrolling element in our component. | ||
// This event should be picked up by the scrollable directive and notify. | ||
// The notification should be picked up by the service. | ||
fixture.componentInstance.scrollingElement.nativeElement.dispatchEvent(new Event('scroll')); |
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.
new Event
isn't supported in IE11
|
||
ngOnInit() { | ||
this._scroll.register(this); | ||
this._elementRef.nativeElement.addEventListener('scroll', (e: Event) => { |
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.
Instead of binding the event directly, I think you could use Observable.fromEvent
. Also I think the same can be done above in the service.
|
||
ngOnInit() { | ||
this._scroll.register(this); | ||
this._elementRef.nativeElement.addEventListener('scroll', (e: Event) => { |
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.
this.renderer.listen(this._elementRef.nativeElement, 'scroll', (e: Event) => {})
|
||
constructor() { | ||
// By default, notify a scroll event when the document is scrolled or the window is resized. | ||
window.document.addEventListener('scroll', this._notify.bind(this)); |
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.
this.renderer.listenGlobal('document', 'scroll', (e: Event) => {})
to make it universal friendly
or it would be even better to use @Component.host
property to listen for events
|
||
constructor() { | ||
// By default, notify a scroll event when the document is scrolled or the window is resized. | ||
window.document.addEventListener('scroll', this._notify.bind(this)); |
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.
It might be better to bind this only after a component has been registered. Also we could unbind it when there are no subscriptions.
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.
I considered this but then we miss out in the case of the document body scrolling without any scrollables on the page. E.g. a long document body with a tooltip.
In the current implementation, the listeners are only attached if at least one component uses another component (e.g. tooltip) that injects the ScrollDispatcher service.
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.
Sounds good. Also something else that I was thinking about: I'm pretty sure that this event will trigger Angular's change detection. It may be worth trying to run it outside Angular's zone, however I'm not sure whether it won't end up not updating the view at the proper times.
378281d
to
26ef99c
Compare
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()); |
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.
still won't work in universal.
Plus I believe u need to unsubscribe from it on destroy
And I seriously don't see why u want to imperatively subscribe here if u can just use @Component.host
. it works in universal and automatically unsubscribes on destroy.
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.
Right now we are not prioritizing universal right now so this may be a concern for another time. Additionally I think there may be some confusion here as this is a service and not a component so it does not have access to a host nor have a lifecycle with destroy.
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.
nor have a lifecycle with destroy.
it's not true. services do have OnDestroy
hook :)
is a service and not a component so it does not have access to a host
right, I missed that part. ignore my comments
d1029e4
to
f426670
Compare
Moved the scroll dispatcher and scrollable directive to the overlay folder. |
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.
LGTM, one nit
constructor(private _overlay: Overlay, private _elementRef: ElementRef, | ||
private _viewContainerRef: ViewContainerRef, private _ngZone: NgZone, | ||
constructor(private _overlay: Overlay, | ||
private _scroll: 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.
_scrollDispatcher
f426670
to
8aefb13
Compare
Changed _scroll to _scrollDispatcher |
8aefb13
to
e22b009
Compare
Rebased |
e22b009
to
95652a6
Compare
f1745d1
to
9159368
Compare
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Includes applying the directive to the sidenav as an example of usage. The tooltip makes use of the scroll service's notification and updates in position.
Would be nice to have some optimization on throttling the scroll events, ideally something similar to throttle but includes the trailing event.