Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle window scroll event in Angular 4?

I can't seem to be able to capture the Window scroll event. On several sites I found code similar to this:

@HostListener("window:scroll", []) onWindowScroll() {   console.log("Scrolling!"); } 

The snippets often come from version 2. This doesn't seem to work (anymore?) in Angular 4.2.2. If I replace "window:scroll" with "window:touchmove" for example, then then touchmove event is handled fine.

Does anyone know what I'm missing? Thank you very much!

like image 438
Robert Avatar asked Jun 13 '17 08:06

Robert


People also ask

How to listen to scroll event in Angular?

Even easier, you can window scroll from any HTML element in the DOM, using the window:scroll event emitter. With this method, you just pass in a function to call when a scroll event fires as you do with any other event listener like mouseenter or click . Simple!

Does scrollIntoView trigger scroll event?

scrollIntoView does not trigger mousewheel nor scroll event in Angular. Save this question.

How do I get scroll position?

To get or set the scroll position of an element, you follow these steps: First, select the element using the selecting methods such as querySelector() . Second, access the scroll position of the element via the scrollLeft and scrollTop properties.

How do you check if an element is visible after scrolling?

To know whether the element is fully visible in viewport, you will need to check whether top >= 0, and bottom is less than the screen height. In a similar way you can also check for partial visibility, top is less than screen height and bottom >= 0. The Javascript code could be written as : window.


2 Answers

Probably your document isn't scrolling, but a div inside it is. The scroll event only bubbles up to the window if it's called from document. Also if you capture the event from document and call something like stopPropagation, you will not receive the event in window.

If you want to capture all the scroll events inside your application, which will also be from tiny scrollable containers, you have to use the default addEventListener method with useCapture set to true.

This will fire the event when it goes down the DOM, instead of the bubble stage. Unfortunately, and quite frankly a big miss, angular does not provide an option to pass in the event listener options, so you have to use the addEventListener:

export class WindowScrollDirective {      ngOnInit() {         window.addEventListener('scroll', this.scroll, true); //third parameter     }      ngOnDestroy() {         window.removeEventListener('scroll', this.scroll, true);     }      scroll = (event): void => {       //handle your scroll here       //notice the 'odd' function assignment to a class field       //this is used to be able to remove the event listener     };  } 

Now this is not all there is to it, because all major browsers (except IE and Edge, obviously) have implemented the new addEventListener spec, which makes it possible to pass an object as third parameter.

With this object you can mark an event listener as passive. This is a recommend thing to do on an event which fires a lot of time, which can interfere with UI performance, like the scroll event. To implement this, you should first check if the current browser supports this feature. On the mozilla.org they've posted a method passiveSupported, with which you can check for browser support. You can only use this though, when you are sure you are not going to use event.preventDefault()

Before I show you how to do that, there is another performance feature you could think of. To prevent change detection from running (the DoCheck gets called every time something async happens within the zone. Like an event firing), you should run your event listener outside the zone, and only enter it when it's really necessary. Soo, let's combine all these things:

export class WindowScrollDirective {      private eventOptions: boolean|{capture?: boolean, passive?: boolean};      constructor(private ngZone: NgZone) {}      ngOnInit() {                     if (passiveSupported()) { //use the implementation on mozilla             this.eventOptions = {                 capture: true,                 passive: true             };         } else {             this.eventOptions = true;         }         this.ngZone.runOutsideAngular(() => {             window.addEventListener('scroll', this.scroll, <any>this.eventOptions);         });     }      ngOnDestroy() {         window.removeEventListener('scroll', this.scroll, <any>this.eventOptions);         //unfortunately the compiler doesn't know yet about this object, so cast to any     }      scroll = (): void => {         if (somethingMajorHasHappenedTimeToTellAngular) {            this.ngZone.run(() => {                this.tellAngular();            });         }     };    } 
like image 61
Poul Kruijt Avatar answered Sep 19 '22 10:09

Poul Kruijt


If you happen to be using Angular Material, you can do this:

import { ScrollDispatchModule } from '@angular/cdk/scrolling'; 

In Ts:

import { ScrollDispatcher } from '@angular/cdk/scrolling';    constructor(private scrollDispatcher: ScrollDispatcher) {         this.scrollDispatcher.scrolled().subscribe(x => console.log('I am scrolling'));   } 

And in Template:

<div cdkScrollable>   <div *ngFor="let one of manyToScrollThru">     {{one}}   </div> </div> 

Reference: https://material.angular.io/cdk/scrolling/overview

like image 36
ttugates Avatar answered Sep 20 '22 10:09

ttugates