Skip to content
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

Merged
merged 6 commits into from
Dec 20, 2016

Conversation

andrewseguin
Copy link
Contributor

@andrewseguin andrewseguin commented Dec 13, 2016

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.

@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Dec 13, 2016
* Scrollable references emit a scrolled event.
*/
@Injectable()
export class Scroll {
Copy link
Member

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();
Copy link
Member

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));
Copy link
Member

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)

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();
Copy link
Member

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?

Copy link
Contributor Author

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]'
Copy link
Member

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 {
Copy link
Member

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'));
Copy link
Member

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) => {
Copy link
Member

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) => {

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));
Copy link

@DzmitryShylovich DzmitryShylovich Dec 13, 2016

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));
Copy link
Member

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.

Copy link
Contributor Author

@andrewseguin andrewseguin Dec 13, 2016

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.

Copy link
Member

@crisbeto crisbeto Dec 13, 2016

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.

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());
Copy link

@DzmitryShylovich DzmitryShylovich Dec 13, 2016

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.

Copy link
Contributor Author

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.

Copy link

@DzmitryShylovich DzmitryShylovich Dec 13, 2016

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

@andrewseguin
Copy link
Contributor Author

Moved the scroll dispatcher and scrollable directive to the overlay folder.

Copy link
Member

@jelbourn jelbourn left a 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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_scrollDispatcher

@andrewseguin
Copy link
Contributor Author

Changed _scroll to _scrollDispatcher

@andrewseguin
Copy link
Contributor Author

Rebased

@jelbourn jelbourn merged commit 9b68e68 into angular:master Dec 20, 2016
@andrewseguin andrewseguin deleted the overlay-scroll branch December 20, 2016 22:57
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants