web-dev-qa-db-fra.com

Défilement automatique dans Angular 2

Je rencontre un problème avec Angular 2 où le passage d'un itinéraire à un autre ne défile pas automatiquement vers le haut de la nouvelle vue. Je me rends compte que Angular 1 permettait à une propriété autoscroll d'être ajoutée à un élément HTML, et d'autres avaient proposé un simple javascript (tel que window.scroll(0, 0)) pour forcer les vues à défiler vers le haut lors du chargement.

Cependant, je ne sais pas comment y parvenir avec Angular 2. Quelqu'un sait-il comment y parvenir?

23
Chris Ryan

mise à jour

Il n'existe actuellement aucun moyen automatique.

Voir aussi Erreur TypeScript Angular 2 lors de l'utilisation de la fonction d'abonnement sur un nouveau routeur (rc 1)

Voir aussi https://github.com/angular/angular/issues/6595#issuecomment-244232725

class MyAppComponent {
  constructor(router: Router) {
    router.events.subscribe(s => {
      if (s instanceof NavigationEnd) {
        const tree = router.parseUrl(router.url);
        if (tree.fragment) {
          // you can use DomAdapter
          const element = document.querySelector("#" + tree.fragment);
          if (element) { element.scrollIntoView(element); }
        }
      }
    });
  }
}

mise à jour

Dans le nouveau routeur V3-beta.2, vous pouvez passer un fragment avec les liens du routeur et la navigation du routeur

<a [routerLink]="..." fragment="top">

il doit y faire défiler mais ajoute également #top à l'URL (pas encore testé moi-même)

Mise à jour

Original

Il existe un problème ouvert couvrant ce sujet https://github.com/angular/angular/issues/6595

Une solution de contournement (mentionnée dans https://github.com/angular/angular/issues/6946 )

Injectez le routeur, abonnez-vous aux modifications de route et appelez le défilement vers le haut:

> = RC.x

router.changes.subscribe() => {
  window.scrollTo(0, 0);
});

bêta

router.events
.filter(e => e instanceof NavigationEnd)
.subscribe(() => {
  window.scrollTo(0, 0);
});
17
Günter Zöchbauer

A vous pouvez voir ici: https://angular.io/docs/ts/latest/api/router/index/Router-class.html#!#events-anchor , vous devez utiliser " router.events.subscribe "depuis Angular 2.0.0

Donc, une bonne solution pour faire automatiquement défiler vers le haut de la page est d'avoir un AppComponent comme ceci:

import {Component} from '@angular/core';
import {Router, NavigationEnd} from "@angular/router";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
    constructor(private router: Router) {
        router.events.subscribe((val) => {
            if (val instanceof NavigationEnd){
                window.scrollTo(0,0);
            }
        });
    }
}
13
Thierry

Les RC plus récents (> = RC.3) ne semblent pas exposer un changes observable, il a probablement depuis été renommé events ou routerEvents .

Leurs documents totalement "fantastiques" ne semblent pas fournir d'informations sur ce qui fait quoi, donc je suppose que vous êtes là pour une petite roulette russe. Ou lancez une pièce ou quelque chose.

D'après la réponse this , il semble que events Observable renvoie des événements concernant l'état de navigation:

router.events.subscribe(event:Event => {
    if(event is NavigationStart) {
    }
    // NavigationEnd
    // NavigationCancel
    // NavigationError
    // RoutesRecognized   
  }
9
the_critic

J'ai utilisé if (this.router.navigated) dans le ngOnInit de chaque page pour déterminer s'il faut ou non utiliser window.scrollTo (0, 0). Cela couvrira la plupart des cas de routage vers la page, tout en laissant la position de défilement où elle devrait être si vous cliquez sur le bouton Retour du navigateur.

if(this.router.navigated) {
  window.scrollTo(0, 0);
}
4
Shane

J'ai eu le même problème. Sur la base de la réponse de Gunter, j'ai découvert que le nouveau routeur 2 RC.1 d'Angular n'expose pas directement un observable. À la place, il possède une propriété changes à cet effet. La solution de contournement pour RC.1 est la suivante:

this._router.changes.subscribe(() => {
    window.scrollTo(0, 0);
}); 
4

Solution 100% testée par mes soins:

constructor(router:Router){
    this.router.events.subscribe(() => {
        window.scrollTo(0, 0);
    });
}
3
RohanArihant

J'ai posté ceci dans le issue thread , mais je le posterai à nouveau ici.

Mon équipe utilise ce que l'équipe angular utilise sur ce dépôt sur angular.io. Il suffit de faire un service et de l'injecter comme d'habitude. Ensuite, sur ngAfterViewInit sur chaque page que vous souhaitez utiliser, appelez simplement ceci. [nom de la variable de service de défilement] .scrollToTop (). Enfin, vous devrez l'ajouter en haut de <body> dans index.html: <div id="top-of-page"></div>

Code de service:

import { Injectable, Inject } from '@angular/core';
import { PlatformLocation } from '@angular/common';
import { DOCUMENT } from '@angular/platform-browser';
import {fromEvent} from 'rxjs/observable/fromEvent';

export const topMargin = 16;
/**
 * A service that scrolls document elements into view
 */
@Injectable()
export class ScrollService {

  private _topOffset: number | null;
  private _topOfPageElement: Element;

  // Offset from the top of the document to bottom of any static elements
  // at the top (e.g. toolbar) + some margin
  get topOffset() {
    if (!this._topOffset) {
      const toolbar = this.document.querySelector('md-toolbar.app-toolbar');
      this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin;
    }
    return this._topOffset;
  }

  get topOfPageElement() {
    if (!this._topOfPageElement) {
      this._topOfPageElement = this.document.getElementById('top-of-page') || this.document.body;
    }
    return this._topOfPageElement;
  }

  constructor(
      @Inject(DOCUMENT) private document: any,
      private location: PlatformLocation) {
    // On resize, the toolbar might change height, so "invalidate" the top offset.
    fromEvent(window, 'resize').subscribe(() => this._topOffset = null);
  }

  /**
   * Scroll to the element with id extracted from the current location hash fragment.
   * Scroll to top if no hash.
   * Don't scroll if hash not found.
   */
  scroll() {
    const hash = this.getCurrentHash();
    const element: HTMLElement = hash
        ? this.document.getElementById(hash)
        : this.topOfPageElement;
    this.scrollToElement(element);
  }

  /**
   * Scroll to the element.
   * Don't scroll if no element.
   */
  scrollToElement(element: Element) {
    if (element) {
      element.scrollIntoView();

      if (window && window.scrollBy) {
        // Scroll as much as necessary to align the top of `element` at `topOffset`.
        // (Usually, `.top` will be 0, except for cases where the element cannot be scrolled all the
        //  way to the top, because the viewport is larger than the height of the content after the
        //  element.)
        window.scrollBy(0, element.getBoundingClientRect().top - this.topOffset);

        // If we are very close to the top (<20px), then scroll all the way up.
        // (This can happen if `element` is at the top of the page, but has a small top-margin.)
        if (window.pageYOffset < 20) {
          window.scrollBy(0, -window.pageYOffset);
        }
      }
    }
  }

  /** Scroll to the top of the document. */
  scrollToTop() {
    this.scrollToElement(this.topOfPageElement);
  }

  /**
   * Return the hash fragment from the `PlatformLocation`, minus the leading `#`.
   */
  private getCurrentHash() {
    return this.location.hash.replace(/^#/, '');
  }
}
2
dockleryxk

Pour ceux d'entre vous qui trouvent que window.scrollTo(0,0) ne fonctionne pas (je suppose à cause de la conception matérielle sidenav mais je suppose totalement) utilisez la méthode trouvée ici: Javascript/CSS window.scrollTo (0,0) ne fonctionne pas

1
Helzgate

J'utilise le matériel sidenav et je n'ai pu obtenir aucune des réponses suggérées pour travailler pour moi. Voici ma solution de travail:

import { Router, NavigationEnd } from '@angular/router';

...

constructor(
  private router: Router,
) {
  router.events.subscribe(event => {
    if (event instanceof NavigationEnd) {
      document.querySelector('.mat-sidenav-content').scrollTop = 0;
    }
  }
}
1
Tyson

Au lieu d'écrire du code dans chaque composant, j'ai ajouté le code suivant au même endroit -

<router-outlet (activate)="onActivate($event)"></router-outlet>

    onActivate(e) {
        window.scrollTo(0, 0);
    }
1
Kanchan