Angular 2 Faites défiler vers le haut sur Route Change

Dans mon application Angular 2, lorsque je fais défiler une page et clique sur le lien au bas de la page, cela change l’itinéraire et me ramène à la page suivante, mais elle ne défile pas en haut de la page. Par conséquent, si la première page est longue et que la deuxième page contient peu de contenu, cela donne l’impression que la deuxième page n’a pas le contenu. Étant donné que le contenu est visible uniquement si l’utilisateur fait défiler vers le haut de la page.

Je peux faire défiler la fenêtre vers le haut de la page dans ngInit du composant, mais existe-t-il une meilleure solution capable de gérer automatiquement tous les itinéraires dans mon application?

Vous pouvez enregistrer un écouteur de changement de route sur votre composant principal et faire défiler les modifications de route vers le haut.

import { Component, OnInit } from '@angular/core'; import { Router, NavigationEnd } from '@angular/router'; @Component({ selector: 'my-app', template: '', }) export class MyAppComponent implements OnInit { constructor(private router: Router) { } ngOnInit() { this.router.events.subscribe((evt) => { if (!(evt instanceof NavigationEnd)) { return; } window.scrollTo(0, 0) }); } } 

Alors que l’excellente réponse de @GuilhermeMeireles corrige le problème initial, elle en introduit un nouveau, en brisant le comportement normal attendu lorsque vous naviguez en arrière ou en avant (avec les boutons du navigateur ou via Location dans le code). Le comportement attendu est que lorsque vous revenez à la page, celle-ci doit restr défilée vers le même emplacement que lorsque vous avez cliqué sur le lien, mais le défilement vers le haut lorsque vous accédez à chaque page enfreint évidemment cette attente.

Le code ci-dessous étend la logique pour détecter ce type de navigation en s’abonnant à la séquence PopStateEvent de Location et en ignorant la logique de défilement si la page nouvellement arrivée est le résultat d’un tel événement.

Si la page à partir de laquelle vous naviguez est suffisamment longue pour couvrir toute la fenêtre, la position de défilement est automatiquement restaurée, mais comme @JordanNelson l’a bien indiqué, si la page est plus courte, vous devez suivre la position de défilement d’origine et la restaurer. explicitement lorsque vous revenez à la page. La version mise à jour du code couvre également ce cas, en restituant toujours explicitement la position de défilement.

 import { Component, OnInit } from '@angular/core'; import { Router, NavigationStart, NavigationEnd } from '@angular/router'; import { Location, PopStateEvent } from "@angular/common"; @Component({ selector: 'my-app', template: '', }) export class MyAppComponent implements OnInit { private lastPoppedUrl: ssortingng; private yScrollStack: number[] = []; constructor(private router: Router, private location: Location) { } ngOnInit() { this.location.subscribe((ev:PopStateEvent) => { this.lastPoppedUrl = ev.url; }); this.router.events.subscribe((ev:any) => { if (ev instanceof NavigationStart) { if (ev.url != this.lastPoppedUrl) this.yScrollStack.push(window.scrollY); } else if (ev instanceof NavigationEnd) { if (ev.url == this.lastPoppedUrl) { this.lastPoppedUrl = undefined; window.scrollTo(0, this.yScrollStack.pop()); } else window.scrollTo(0, 0); } }); } } 

Vous pouvez écrire ceci plus succinctement en profitant de la méthode du filter observable:

 this.router.events.filter(event => event instanceof NavigationEnd).subscribe(() => { this.window.scrollTo(0, 0); }); 

Si vous avez des problèmes pour faire défiler vers le haut lorsque vous utilisez le matériau angular 2, cela vous aidera. La fenêtre ou le corps du document n’aura pas la barre de défilement, vous devez donc récupérer le sidenav contenu sidenav et faire défiler cet élément, sinon essayez de faire défiler la fenêtre par défaut.

 this.router.events.filter(event => event instanceof NavigationEnd) .subscribe(() => { const contentContainer = document.querySelector('.mat-sidenav-content') || this.window; contentContainer.scrollTo(0, 0); }); 

De plus, le CDK angular v6.x a maintenant un paquet de défilement qui pourrait aider à gérer le défilement.

Si vous avez un rendu côté serveur, veillez à ne pas exécuter le code en utilisant windows sur le serveur, où cette variable n’existe pas. Cela entraînerait une rupture de code.

 export class AppComponent implements { routerSubscription: Subscription; constructor(private router: Router, @Inject(PLATFORM_ID) private platformId: any) {} ngOnInit() { if (isPlatformBrowser(this.platformId)) { this.routerSubscription = this.router.events .filter(event => event instanceof NavigationEnd) .subscribe(event => { window.scrollTo(0, 0); }); } } ngOnDestroy() { this.routerSubscription.unsubscribe(); } } 

isPlatformBrowser est une fonction utilisée pour vérifier si la plate-forme actuelle où l’application est rendue est un navigateur ou non. Nous lui donnons le platformId injecté.

Il est également possible de vérifier l’existence de windows variables, pour être sûr, comme ceci:

 if (typeof window != 'undefined') 

il suffit de faire simple avec l’action clic

dans votre composant principal html fait référence à #scrollContainer

 

dans la composante principale

 onActivate(e, scrollContainer) { scrollContainer.scrollTop = 0; } 

La meilleure réponse réside dans la discussion Angular GitHub (le changement d’itinéraire ne fait pas défiler la page vers le haut ).

Peut-être que vous voulez aller en haut seulement dans les changements de routeur racine (pas chez les enfants, parce que vous pouvez charger des routes avec une charge paresseuse dans un tableau)

app.component.html

  

app.component.ts

 onDeactivate() { document.body.scrollTop = 0; // Alternatively, you can scroll to top by using this other call: // window.scrollTo(0, 0) } 

Crédits complets à JoniJnm (article original )

A partir de Angular 6.1, vous pouvez maintenant éviter les problèmes et transmettre extraOptions à votre RouterModule.forRoot() tant que second paramètre et pouvez spécifier

scrollPositionRestoration: enabled

pour indiquer à Angular de faire défiler vers le haut chaque fois que l’itinéraire change.

 @NgModule({ imports: [ RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled', }) ], ... 

Documents officiels angulars

Vous pouvez append le hook de cycle de vie AfterViewInit à votre composant.

 ngAfterViewInit() { window.scrollTo(0, 0); } 

Voici une solution que j’ai trouvée. J’ai associé la LocationStrategy aux événements du routeur. Utilisation de LocationStrategy pour définir un booléen pour savoir quand un utilisateur traverse actuellement l’historique du navigateur. De cette façon, je n’ai pas besoin de stocker un tas de données URL et y-scroll (ce qui ne fonctionne pas du tout, car chaque donnée est remplacée en fonction de l’URL). Cela résout également le problème lorsque l’utilisateur décide de maintenir le bouton Précédent ou Suivant sur un navigateur et de revenir ou de transférer plusieurs pages au lieu d’une seule.

PS Je n’ai testé que sur la dernière version d’IE, Chrome, FireFox, Safari et Opera (à partir de ce post).

J’espère que cela t’aides.

 export class AppComponent implements OnInit { isPopState = false; constructor(private router: Router, private locStrat: LocationStrategy) { } ngOnInit(): void { this.locStrat.onPopState(() => { this.isPopState = true; }); this.router.events.subscribe(event => { // Scroll to top if accessing a page, not via browser history stack if (event instanceof NavigationEnd && !this.isPopState) { window.scrollTo(0, 0); this.isPopState = false; } // Ensures that isPopState is reset if (event instanceof NavigationEnd) { this.isPopState = false; } }); } } 

Cette solution est basée sur la solution de @ FernandoEcheverria et @ GuilhermeMeireles, mais elle est plus concise et fonctionne avec les mécanismes popstate fournis par Angular Router. Cela permet de stocker et de restaurer le niveau de défilement de plusieurs navigations consécutives.

Nous stockons les positions de défilement pour chaque état de navigation dans une carte scrollLevels . Une fois qu’il y a un événement popstate, l’ID de l’état sur le point d’être restauré est fourni par le routeur angular: event.restoredState.navigationId . Ceci est ensuite utilisé pour obtenir le dernier niveau de défilement de cet état à partir de scrollLevels .

S’il n’y a pas de niveau de défilement stocké pour l’itinéraire, celui-ci défilera vers le haut comme prévu.

 import { Component, OnInit } from '@angular/core'; import { Router, NavigationStart, NavigationEnd } from '@angular/router'; @Component({ selector: 'my-app', template: '', }) export class AppComponent implements OnInit { constructor(private router: Router) { } ngOnInit() { const scrollLevels: { [navigationId: number]: number } = {}; let lastId = 0; let restoredId: number; this.router.events.subscribe((event: Event) => { if (event instanceof NavigationStart) { scrollLevels[lastId] = window.scrollY; lastId = event.id; restoredId = event.restoredState ? event.restoredState.navigationId : undefined; } if (event instanceof NavigationEnd) { if (restoredId) { // Optional: Wrap a timeout around the next line to wait for // the component to finish loading window.scrollTo(0, scrollLevels[restoredId] || 0); } else { window.scrollTo(0, 0); } } }); } } 

Si vous avez simplement besoin de faire défiler la page vers le haut, vous pouvez le faire (pas la meilleure solution, mais rapidement)

 document.getElementById('elementId').scrollTop = 0; 

pour iphone / ios safari, vous pouvez emballer avec un setTimeout

 setTimeout(function(){ window.scrollTo(0, 1); }, 0); 

@Fernando Echeverria super! mais ce code ne fonctionne pas dans le routeur de hachage ou le routeur paresseux. parce qu’ils ne déclenchent pas de changement de lieu. peut essayer ceci:

 private lastRouteUrl: ssortingng[] = [] ngOnInit(): void { this.router.events.subscribe((ev) => { const len = this.lastRouteUrl.length if (ev instanceof NavigationEnd) { this.lastRouteUrl.push(ev.url) if (len > 1 && ev.url === this.lastRouteUrl[len - 2]) { return } window.scrollTo(0, 0) } }) } 

L’utilisation du Router lui-même entraînera des problèmes que vous ne pouvez pas résoudre complètement pour maintenir une expérience de navigation cohérente. A mon avis, la meilleure méthode consiste à utiliser une directive personnalisée et à laisser cela réinitialiser le défilement en cliquant. La bonne chose à ce sujet, c’est que si vous êtes sur la même url que celle sur laquelle vous cliquez, la page défile également vers le haut. Ceci est cohérent avec les sites Web normaux. La directive base pourrait ressembler à ceci:

 import {Directive, HostListener} from '@angular/core'; @Directive({ selector: '[linkToTop]' }) export class LinkToTopDirective { @HostListener('click') onClick(): void { window.scrollTo(0, 0); } } 

Avec l’utilisation suivante:

  

Cela suffira pour la plupart des cas d’utilisation, mais je peux imaginer quelques problèmes qui pourraient en découler:

  • Ne fonctionne pas sur universal cause de l’utilisation de la window
  • Impact de petite vitesse sur la détection des changements, car il est déclenché à chaque clic
  • Aucun moyen de désactiver cette directive

Il est en fait assez facile de surmonter ces problèmes:

 @Directive({ selector: '[linkToTop]' }) export class LinkToTopDirective implements OnInit, OnDestroy { @Input() set linkToTop(active: ssortingng | boolean) { this.active = typeof active === 'ssortingng' ? active.length === 0 : active; } private active: boolean = true; private onClick: EventListener = (event: MouseEvent) => { if (this.active) { window.scrollTo(0, 0); } }; constructor(@Inject(PLATFORM_ID) private readonly platformId: Object, private readonly elementRef: ElementRef, private readonly ngZone: NgZone ) {} ngOnDestroy(): void { if (isPlatformBrowser(this.platformId)) { this.elementRef.nativeElement.removeEventListener('click', this.onClick, false); } } ngOnInit(): void { if (isPlatformBrowser(this.platformId)) { this.ngZone.runOutsideAngular(() => this.elementRef.nativeElement.addEventListener('click', this.onClick, false) ); } } } 

Cela prend en compte la plupart des cas d’utilisation, avec le même usage que celui de base, avec l’avantage de l’activer / de le désactiver:

     

publicités, ne lisez pas si vous ne voulez pas être annoncé

Une autre amélioration pourrait être apscope pour vérifier si le navigateur prend en charge ou non les événements passive . Cela complique un peu plus le code et est un peu obscur si vous souhaitez implémenter tout cela dans vos directives / modèles personnalisés. C’est pourquoi j’ai écrit une petite bibliothèque que vous pouvez utiliser pour résoudre ces problèmes. Pour avoir les mêmes fonctionnalités que ci-dessus, et avec l’événement passive ajouté, vous pouvez modifier votre directive à ceci, si vous utilisez la bibliothèque ng-event-options . La logique est à l’intérieur de l’écouteur click.pnb :

 @Directive({ selector: '[linkToTop]' }) export class LinkToTopDirective { @Input() set linkToTop(active: ssortingng|boolean) { this.active = typeof active === 'ssortingng' ? active.length === 0 : active; } private active: boolean = true; @HostListener('click.pnb') onClick(): void { if (this.active) { window.scrollTo(0, 0); } } } 

Salut les gars cela fonctionne pour moi en angular 4. Il suffit de référencer le parent pour faire défiler le changement de routeur`

layout.component.pug

 .wrapper(#outlet="") router-outlet((activate)='routerActivate($event,outlet)') 

layout.component.ts

  public routerActivate(event,outlet){ outlet.scrollTop = 0; }` 

Cela a fonctionné pour moi mieux pour tous les changements de navigation, y compris la navigation de hachage

 constructor(private route: ActivatedRoute) {} ngOnInit() { this._sub = this.route.fragment.subscribe((hash: ssortingng) => { if (hash) { const cmp = document.getElementById(hash); if (cmp) { cmp.scrollIntoView(); } } else { window.scrollTo(0, 0); } }); } 

L’idée principale derrière ce code est de garder toutes les URL visitées avec les données scrolly respectives dans un tableau. Chaque fois qu’un utilisateur abandonne une page (NavigationStart), ce tableau est mis à jour. Chaque fois qu’un utilisateur entre une nouvelle page (NavigationEnd), nous décidons de restaurer la position Y ou non selon la manière dont nous arrivons à cette page. Si une référence a été utilisée sur une page, nous passons à 0. Si les fonctionnalités de navigation du navigateur sont utilisées, nous faisons défiler jusqu’à Y dans notre tableau. Désolé pour mon anglais 🙂

 import { Component, OnInit, OnDestroy } from '@angular/core'; import { Location, PopStateEvent } from '@angular/common'; import { Router, Route, RouterLink, NavigationStart, NavigationEnd, RouterEvent } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; @Component({ selector: 'my-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { private _subscription: Subscription; private _scrollHistory: { url: ssortingng, y: number }[] = []; private _useHistory = false; constructor( private _router: Router, private _location: Location) { } public ngOnInit() { this._subscription = this._router.events.subscribe((event: any) => { if (event instanceof NavigationStart) { const currentUrl = (this._location.path() !== '') this._location.path() : '/'; const item = this._scrollHistory.find(x => x.url === currentUrl); if (item) { item.y = window.scrollY; } else { this._scrollHistory.push({ url: currentUrl, y: window.scrollY }); } return; } if (event instanceof NavigationEnd) { if (this._useHistory) { this._useHistory = false; window.scrollTo(0, this._scrollHistory.find(x => x.url === event.url).y); } else { window.scrollTo(0, 0); } } }); this._subscription.add(this._location.subscribe((event: PopStateEvent) => { this._useHistory = true; })); } public ngOnDestroy(): void { this._subscription.unsubscribe(); } }