web-dev-qa-db-fra.com

Comment gérer un événement de défilement de fenêtre dans Angular 4?

Je n'arrive pas à capturer l'événement de défilement de fenêtre . Sur plusieurs sites, j'ai trouvé un code similaire à celui-ci:

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

Les extraits proviennent souvent de la version 2. Cela ne semble pas fonctionner (plus?) Dans Angular 4.2.2. Si je remplace "window: scroll" par "window: touchmove" par exemple, alors l'événement touchmove est géré correctement. 

Est-ce que quelqu'un sait ce que je manque? Merci beaucoup!

18
Robert

Probablement, votre document ne fait pas défiler, mais un div à l'intérieur. L'événement de défilement bouillonne jusqu'à window s'il est appelé à partir de document. De même, si vous capturez l'événement à partir de document et appelez quelque chose comme stopPropagation, vous ne recevrez pas l'événement dans window.

Si vous souhaitez capturer tous les événements de défilement dans votre application, qui proviendront également de petits conteneurs défilables, vous devez utiliser la méthode addEventListener par défaut avec useCapture défini sur true

Cela déclenchera l’événement quand il descendra la DOM, au lieu du stade de bulle. Malheureusement, et franchement un gros raté, angular ne fournit pas d’option permettant de transmettre les options d’écoute d’événements. Vous devez donc utiliser la variable addEventListener:

export class WindowScrollDirective {

    ngOnInit() {
        window.addEventListener('scroll', this.scroll, true); //third parameter
    }

    ngOnDestroy() {
        window.removeEventListener('scroll', this.scroll, true);
    }

    scroll = (): 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
    };

}

Maintenant, ce n’est pas tout, car tous les principaux navigateurs (à l’exception de IE et Edge, bien sûr) ont implémenté la nouvelle spécification addEventListener, qui permet de passer un objet en tant que troisième paramètre

Avec cet objet, vous pouvez marquer un écouteur d'événement avec passive. Il est recommandé de le faire sur un événement qui prend beaucoup de temps, ce qui peut interférer avec les performances de l'interface utilisateur, comme l'événement de défilement. Pour implémenter cela, vous devez d’abord vérifier si le navigateur actuel prend en charge cette fonctionnalité. Sur le site mozilla.org, ils ont publié une méthode passiveSupported, avec laquelle vous pouvez vérifier la prise en charge du navigateur. Vous ne pouvez l’utiliser que si vous êtes certain de ne pas utiliser event.preventDefault()

Avant de vous montrer comment faire cela, vous pouvez penser à une autre fonction de performance. Pour empêcher la détection des modifications de s'exécuter (la DoCheck est appelée chaque fois qu'un événement asynchrone se produit dans la zone. Comme un événement déclenché), vous devez exécuter votre écouteur d'événement en dehors de la zone et ne l'entrer que lorsque c'est vraiment nécessaire. Soo, combinons toutes ces choses:

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();
           });
        }
    };   
}
55
PierreDuc

Si vous êtes en utilisant un matériau angulaire, vous pouvez le faire:

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

En Ts:

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

  constructor(private scrollDispatcher: ScrollDispatcher) {    
    this.scrollDispatcher.scrolled().subscribe(x => console.log('I am scrolling'));
  }

Et dans le modèle:

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

Référence: https://material.angular.io/cdk/scrolling/overview

1
ttugates

Je ne suis pas autorisé à commenter encore. @PierreDuc, votre réponse est parfaite, sauf que @Robert a déclaré que le document ne défilait pas. J'ai légèrement modifié votre réponse pour utiliser l'événement envoyé par le programme d'écoute, puis surveiller l'élément source.

 ngOnInit() {
    window.addEventListener('scroll', this.scrollEvent, true);
  }

  ngOnDestroy() {
    window.removeEventListener('scroll', this.scrollEvent, true);
  }

  scrollEvent = (event: any): void => {
    const number = event.srcElement.scrollTop;
  }
1
Jason