Angular 5 Faites défiler vers le haut à chaque clic sur l'itinéraire

103

J'utilise angular 5. J'ai un tableau de bord où j'ai quelques sections avec un petit contenu et quelques sections avec un contenu si grand que je suis confronté à un problème lors du changement de routeur en allant en haut. Chaque fois que j'ai besoin de faire défiler pour aller en haut. Quelqu'un peut-il m'aider à résoudre ce problème afin que lorsque je change de routeur, ma vue reste toujours au sommet.

Merci d'avance.

raihan
la source
Copie

Réponses:

218

Il existe quelques solutions, assurez-vous de toutes les vérifier :)


La sortie du routeur émettra l' activateévénement à chaque fois qu'un nouveau composant est instancié, nous pourrions donc utiliser (activate)pour faire défiler (par exemple) vers le haut:

app.component.html

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

app.component.ts

onActivate(event) {
    window.scroll(0,0);
    //or document.body.scrollTop = 0;
    //or document.querySelector('body').scrollTo(0,0)
    ...
}

Utilisez, par exemple, cette solution pour un défilement fluide:

    onActivate(event) {
        let scrollToTop = window.setInterval(() => {
            let pos = window.pageYOffset;
            if (pos > 0) {
                window.scrollTo(0, pos - 20); // how far to scroll on each step
            } else {
                window.clearInterval(scrollToTop);
            }
        }, 16);
    }

Si vous souhaitez être sélectif, disons que tous les composants ne doivent pas déclencher le défilement, vous pouvez le vérifier dans une ifinstruction comme celle-ci:

onActivate(e) {
    if (e.constructor.name)==="login"{ // for example
            window.scroll(0,0);
    }
}

Depuis Angular6.1, nous pouvons également utiliser { scrollPositionRestoration: 'enabled' }sur des modules chargés avec impatience et il sera appliqué à toutes les routes:

RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled' })

Il fera également déjà le défilement fluide. Cependant, cela présente l'inconvénient de le faire sur chaque routage.


Une autre solution consiste à faire le défilement supérieur sur l'animation du routeur. Ajoutez ceci dans chaque transition où vous souhaitez faire défiler vers le haut:

query(':enter, :leave', style({ position: 'fixed' }), { optional: true }) 
Vega
la source
4
ça marche . Merci pour votre réponse. vous gagnez beaucoup de temps pour moi
raihan
2
les événements de défilement sur l' windowobjet ne fonctionnent pas en angulaire 5. Des suppositions pourquoi?
Sahil Babbar
2
Un vrai héros ici. A sauvé des heures de frustration. Je vous remercie!
Anjana Silva
1
@AnjanaSilva, merci pour le commentaire positif! Je suis très heureux que cela puisse vous aider :)
Vega
2
Existe-t-il un moyen pour le module chargé paresseusement?
Pankaj Prakash
54

Si vous rencontrez ce problème dans Angular 6, vous pouvez le résoudre en ajoutant le paramètre scrollPositionRestoration: 'enabled'au RouterModule de app-routing.module.ts:

@NgModule({
  imports: [RouterModule.forRoot(routes,{
    scrollPositionRestoration: 'enabled'
  })],
  exports: [RouterModule]
})
Nimezzz
la source
7
Veuillez ne pas publier de photos de code. Copiez-collez simplement le code lui-même dans votre réponse.
mypetlion
2
Sûr. La prochaine fois, je le ferai. Je suis juste un peu nouveau ici. :)
Nimezzz
4
Notez qu'à partir du 6 novembre 2019 en utilisant Angular 8, au moins, la scrollPositionRestorationpropriété ne fonctionne pas avec le contenu de page dynamique (c'est-à-dire lorsque le contenu de la page est chargé de manière asynchrone): voir ce rapport de bogue Angular: github.com/angular/angular / issues / 24547
dbeachy1
25

EDIT: Pour Angular 6+, veuillez utiliser la réponse de Nimesh Nishara Indimagedara en mentionnant:

RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled'
});

Réponse originale:

Si tout échoue, créez un élément HTML vide (par exemple: div) en haut (ou faites défiler jusqu'à l'emplacement souhaité) avec id = "top" sur le modèle (ou le modèle parent):

<div id="top"></div>

Et en composant:

  ngAfterViewInit() {
    // Hack: Scrolls to top of Page after page view initialized
    let top = document.getElementById('top');
    if (top !== null) {
      top.scrollIntoView();
      top = null;
    }
  }
GeoRover
la source
2
Cette solution a fonctionné pour moi (testée sur Chrome ainsi que Edge). La solution acceptée n'a pas fonctionné pour mon projet (Angular5)
Rob van Meeuwen
@RobvanMeeuwen, Si ma réponse n'a pas fonctionné, vous ne l'avez probablement pas mise en œuvre de la même manière. Cette solution manipule directement le DOM qui n'est pas correct non plus en toute sécurité
Vega
@Vega, c'est pourquoi je l'ai appelé un hack. Votre solution est la bonne. Certaines personnes ici n'ont pas pu implémenter le vôtre, alors j'ai proposé le hack de secours. Ils doivent refactoriser leur code en fonction de la version sur laquelle ils se trouvent à ce stade.
GeoRover
De toute cette solution est un travail pour moi. Merci @GeoRover
Gangani Roshan
Pour Angular 6+, veuillez utiliser la réponse de Nimesh Nishara Indimagedara.
GeoRover
6

Bien que @Vega fournisse la réponse directe à votre question, il y a des problèmes. Il casse le bouton Précédent / Suivant du navigateur. Si votre utilisateur clique sur le bouton Précédent ou Suivant du navigateur, il perd sa place et se fait défiler en haut. Cela peut être un peu pénible pour vos utilisateurs s'ils devaient faire défiler vers le bas pour accéder à un lien et décidaient de cliquer en arrière uniquement pour trouver que la barre de défilement avait été réinitialisée en haut.

Voici ma solution au problème.

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;
      }
    });
  }
}
Sal_Vader_808
la source
2
Merci pour le code avancé et la bonne solution. Mais parfois, la solution @Vega est meilleure, car elle résout de nombreux problèmes d'animation et de hauteur de page dynamique. Votre solution est bonne si vous avez une longue page avec du contenu et une animation de routage simple. Je l'essaie sur la page avec de nombreux blocs d'animation et de dynamique et ça n'a pas l'air si bon. Je pense que parfois nous pouvons sacrifier la «position arrière» pour notre application. Mais sinon - votre solution est la meilleure que je vois pour Angular. Merci encore
Denis Savenko
5

Dans mon cas, je viens d'ajouter

window.scroll(0,0);

dans ngOnInit()et son fonctionnement très bien.

Zohab Ali
la source
4

À partir de Angular Version 6+ Pas besoin d'utiliser window.scroll (0,0)

Pour la version angulaire 6+de @ Représente les options de configuration du routeur.docs

interface ExtraOptions {
  enableTracing?: boolean
  useHash?: boolean
  initialNavigation?: InitialNavigation
  errorHandler?: ErrorHandler
  preloadingStrategy?: any
  onSameUrlNavigation?: 'reload' | 'ignore'
  scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'
  anchorScrolling?: 'disabled' | 'enabled'
  scrollOffset?: [number, number] | (() => [number, number])
  paramsInheritanceStrategy?: 'emptyOnly' | 'always'
  malformedUriErrorHandler?: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree
  urlUpdateStrategy?: 'deferred' | 'eager'
  relativeLinkResolution?: 'legacy' | 'corrected'
}

On peut utiliser scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'dans

Exemple:

RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled'|'top' 
});

Et si l'on a besoin de contrôler manuellement le défilement, pas besoin d'utiliser à la window.scroll(0,0) place du package commun Angular V6 ViewPortScoller.

abstract class ViewportScroller {
  static ngInjectableDef: defineInjectable({ providedIn: 'root', factory: () => new BrowserViewportScroller(inject(DOCUMENT), window) })
  abstract setOffset(offset: [number, number] | (() => [number, number])): void
  abstract getScrollPosition(): [number, number]
  abstract scrollToPosition(position: [number, number]): void
  abstract scrollToAnchor(anchor: string): void
  abstract setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void
}

L'utilisation est assez simple Exemple:

import { Router } from '@angular/router';
import {  ViewportScroller } from '@angular/common'; //import
export class RouteService {

  private applicationInitialRoutes: Routes;
  constructor(
    private router: Router;
    private viewPortScroller: ViewportScroller//inject
  )
  {
   this.router.events.pipe(
            filter(event => event instanceof NavigationEnd))
            .subscribe(() => this.viewPortScroller.scrollToPosition([0, 0]));
}
Vikas
la source
4

si vous utilisez mat-sidenav, donnez un identifiant à la sortie du routeur (si vous avez des sorties de routeur parent et enfant) et utilisez la fonction activate <router-outlet id="main-content" (activate)="onActivate($event)"> et utilisez ce sélecteur de requête `` mat-sidenav-content '' pour faire défiler vers le haut onActivate(event) { document.querySelector("mat-sidenav-content").scrollTo(0, 0); }

Ivin Antony
la source
Fonctionne très bien même sans utiliser un id (j'en ai un router-outletsur mon application). Je l'ai aussi fait d'une manière plus `` angulaire '': @ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer; onActivate() { this.sidenavContainer.scrollable.scrollTo({ left: 0, top: 0 }); }
GCSDC
3

Je continue de chercher une solution intégrée à ce problème, comme dans AngularJS. Mais jusque-là, cette solution fonctionne pour moi, c'est simple et préserve la fonctionnalité du bouton de retour.

app.component.html

<router-outlet (deactivate)="onDeactivate()"></router-outlet>

app.component.ts

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

Réponse de zurfyx original post

Jared Whipple
la source
3

Il vous suffit de créer une fonction qui contient le réglage du défilement de votre écran

par exemple

window.scroll(0,0) OR window.scrollTo() by passing appropriate parameter.

window.scrollTo (xpos, ypos) -> paramètre attendu.

Vijay Barot
la source
2

Voici une solution qui ne défile vers le haut du composant que lors de la première visite pour CHAQUE composant (au cas où vous auriez besoin de faire quelque chose de différent par composant):

Dans chaque composant:

export class MyComponent implements OnInit {

firstLoad: boolean = true;

...

ngOnInit() {

  if(this.firstLoad) {
    window.scroll(0,0);
    this.firstLoad = false;
  }
  ...
}
0_tr0jan_0
la source
2

export class AppComponent {
  constructor(private router: Router) {
    router.events.subscribe((val) => {
      if (val instanceof NavigationEnd) {
        window.scrollTo(0, 0);
      }
    });
  }

}

irhétorique
la source
2

Angular 6.1 et versions ultérieures:

Vous pouvez utiliser la solution intégrée disponible dans Angular 6.1+ avec option scrollPositionRestoration: 'enabled'pour obtenir la même chose.

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

Angular 6.0 et versions antérieures:

import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { Location, PopStateEvent } from "@angular/common";

@Component({
    selector: 'my-app',
    template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {

    private lastPoppedUrl: string;
    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);
            }
        });
    }
}

Remarque: le comportement attendu est que lorsque vous revenez à la page, elle doit rester défilée vers le bas jusqu'au même emplacement que lorsque vous avez cliqué sur le lien, mais défiler vers le haut lorsque vous arrivez sur chaque page.

s sharif
la source
2

il suffit d'ajouter

window.scrollTo({ top: 0);

à ngOnInit ()

Max Hesari
la source
window.scroll (0,0)
Akitha_MJ
2

Pour quelqu'un qui recherche une fonction de défilement, ajoutez simplement la fonction et appelez-la quand vous en avez besoin

scrollbarTop(){

  window.scroll(0,0);
}
Akitha_MJ
la source
1

Essaye ça:

app.component.ts

import {Component, OnInit, OnDestroy} from '@angular/core';
import {Router, NavigationEnd} from '@angular/router';
import {filter} from 'rxjs/operators';
import {Subscription} from 'rxjs';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy {
    subscription: Subscription;

    constructor(private router: Router) {
    }

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

    ngOnDestroy() {
        this.subscription.unsubscribe();
    }
}
Eray T
la source
1

Composant: Abonnez-vous à tous les événements de routage au lieu de créer une action dans le modèle et faites défiler NavigationEnd b / c, sinon vous le déclencherez sur de mauvaises navigations ou des routes bloquées, etc. C'est un moyen sûr de savoir que si une route est parcourue avec succès, puis faites défiler. Sinon, ne faites rien.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {

  router$: Subscription;

  constructor(private router: Router) {}

  ngOnInit() {
    this.router$ = this.router.events.subscribe(next => this.onRouteUpdated(next));
  }

  ngOnDestroy() {
    if (this.router$ != null) {
      this.router$.unsubscribe();
    }
  }

  private onRouteUpdated(event: any): void {
    if (event instanceof NavigationEnd) {
      this.smoothScrollTop();
    }
  }

  private smoothScrollTop(): void {
    const scrollToTop = window.setInterval(() => {
      const pos: number = window.pageYOffset;
      if (pos > 0) {
          window.scrollTo(0, pos - 20); // how far to scroll on each step
      } else {
          window.clearInterval(scrollToTop);
      }
    }, 16);
  }

}

HTML

<router-outlet></router-outlet>
Joshua Michael Wagoner
la source
1

essaye ça

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

ce code pris en charge angulaire 6 <=

Rokive
la source