Angular 5 Desplácese hacia arriba en cada clic de ruta

104

Estoy usando angular 5. Tengo un tablero en el que tengo pocas secciones con contenido pequeño y pocas secciones con contenido tan grande que me enfrento a un problema al cambiar el enrutador mientras voy a la parte superior. Cada vez que necesito desplazarme para ir al principio. ¿Alguien puede ayudarme a resolver este problema para que cuando cambie el enrutador mi vista siempre permanezca en la parte superior?

Gracias por adelantado.

Raihan
fuente

Respuestas:

218

Hay algunas soluciones, asegúrese de revisarlas todas :)


La salida del enrutador emitirá el activateevento cada vez que se cree una instancia de un nuevo componente, por lo que podríamos usar (activate)para desplazarnos (por ejemplo) hacia la parte superior:

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)
    ...
}

Utilice, por ejemplo, esta solución para un desplazamiento suave:

    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 desea ser selectivo, digamos que no todos los componentes deben activar el desplazamiento, puede verificarlo en una ifdeclaración como esta:

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

Desde Angular6.1, también podemos usar { scrollPositionRestoration: 'enabled' }en módulos cargados con entusiasmo y se aplicará a todas las rutas:

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

También hará el desplazamiento suave, ya. Sin embargo, esto tiene el inconveniente de hacerlo en todas las rutas.


Otra solución es hacer el desplazamiento superior en la animación del enrutador. Agregue esto en cada transición donde desee desplazarse hacia la parte superior:

query(':enter, :leave', style({ position: 'fixed' }), { optional: true }) 
Vega
fuente
4
esta funcionando . Gracias por tu respuesta. me ahorras mucho tiempo
raihan
2
los eventos de desplazamiento en el windowobjeto no funcionan en angular 5. ¿Alguna conjetura por qué?
Sahil Babbar
2
Un verdadero héroe aquí. Ahorró posibles horas de frustración. ¡Gracias!
Anjana Silva
1
@AnjanaSilva, ¡gracias por el comentario positivo! Estoy muy contento de que pueda ayudarte :)
Vega
2
¿Hay alguna forma de módulo de carga diferida?
Pankaj Prakash
54

Si enfrenta este problema en Angular 6, puede solucionarlo agregando el parámetro scrollPositionRestoration: 'enabled'al RouterModule de app-routing.module.ts:

@NgModule({
  imports: [RouterModule.forRoot(routes,{
    scrollPositionRestoration: 'enabled'
  })],
  exports: [RouterModule]
})
Nimezzz
fuente
7
No publique imágenes de código. Simplemente copie y pegue el código directamente en su respuesta.
mypetlion
2
Por supuesto. A la próxima, lo haré. Soy un poco nuevo aquí. :)
Nimezzz
4
Tenga en cuenta que a partir del 6 de noviembre de 2019 con Angular 8, al menos, la scrollPositionRestorationpropiedad no funciona con contenido de página dinámico (es decir, donde el contenido de la página se carga de forma asincrónica): consulte este informe de error de Angular: github.com/angular/angular / issues / 24547
dbeachy1
25

EDITAR: Para Angular 6+, use la respuesta de Nimesh Nishara Indimagedara mencionando:

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

Respuesta original:

Si todo falla, cree algún elemento HTML vacío (por ejemplo: div) en la parte superior (o el desplazamiento deseado a la ubicación) con id = "top" en la plantilla (o plantilla principal):

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

Y en componente:

  ngAfterViewInit() {
    // Hack: Scrolls to top of Page after page view initialized
    let top = document.getElementById('top');
    if (top !== null) {
      top.scrollIntoView();
      top = null;
    }
  }
GeoRover
fuente
2
Esta solución funcionó para mí (probada en Chrome y Edge). La solución aceptada no funcionó para mi proyecto (Angular5)
Rob van Meeuwen
@RobvanMeeuwen, si mi respuesta no funcionó, probablemente no la implementó de la misma manera. Esta solución está manipulando directamente el DOM, lo cual no es correcto ni seguro
Vega
@Vega, por eso lo llamé truco. Tu solución es la adecuada. Algunas personas aquí no pudieron implementar el tuyo, así que ofrecí el truco alternativo. Deben refactorizar su código según la versión en la que se encuentran en este momento.
GeoRover
De toda esta solución es trabajo para mí. Gracias @GeoRover
Gangani Roshan
Para Angular 6+, use la respuesta de Nimesh Nishara Indimagedara.
GeoRover
6

Aunque @Vega proporciona la respuesta directa a su pregunta, existen problemas. Rompe el botón de avance / retroceso del navegador. Si su usuario hace clic en el botón de avance o retroceso del navegador, pierde su lugar y se desplaza hacia la parte superior. Esto puede ser un poco molesto para sus usuarios si tuvieran que desplazarse hacia abajo para llegar a un enlace y decidieran hacer clic hacia atrás solo para encontrar que la barra de desplazamiento se había restablecido a la parte superior.

Aquí está mi solución al problema.

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
fuente
2
Gracias por el código avanzado y la buena solución. Pero a veces la solución @Vega es mejor, porque resuelve muchos problemas con la animación y la altura dinámica de la página. Su solución es buena si tiene una página larga con contenido y una animación de enrutamiento simple. Lo intento en una página con muchos bloques de animación y dinámica y no se ve tan bien. Creo que a veces podemos sacrificar la "posición trasera" por nuestra aplicación. Pero si no, su solución es la mejor que veo para Angular. Gracias de nuevo
Denis Savenko
5

En mi caso acabo de agregar

window.scroll(0,0);

en ngOnInit()y su funcionamiento muy bien.

Zohab Ali
fuente
4

Desde la versión angular 6+ No es necesario usar window.scroll (0,0)

Para la versión angular 6+de @ Representa opciones para configurar el enrutador.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'
}

Uno puede usar scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'en

Ejemplo:

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

Y si uno requiere controlar manualmente el desplazamiento, no es necesario usar window.scroll(0,0) el paquete común en lugar de 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
}

El uso es bastante sencillo Ejemplo:

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
fuente
4

Si usa mat-sidenav, dé una identificación a la salida del enrutador (si tiene salidas de enrutador para padres e hijos) y use la función de activación <router-outlet id="main-content" (activate)="onActivate($event)"> y use este selector de consultas 'mat-sidenav-content' para desplazarse hacia arriba onActivate(event) { document.querySelector("mat-sidenav-content").scrollTo(0, 0); }

ivin antony
fuente
Funciona muy bien incluso sin usar un id (tengo uno router-outleten mi aplicación). También lo hice de una manera más 'angular': @ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer; onActivate() { this.sidenavContainer.scrollable.scrollTo({ left: 0, top: 0 }); }
GCSDC
3

Sigo buscando una solución integrada para este problema como la que hay en AngularJS. Pero hasta entonces, esta solución funciona para mí, es simple y conserva la funcionalidad del botón de retroceso.

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)
}

Respuesta de la publicación original de zurfyx

Jared Whipple
fuente
3

Solo necesita crear una función que contenga el ajuste del desplazamiento de su pantalla

por ejemplo

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

window.scrollTo (xpos, ypos) -> parámetro esperado.

Vijay Barot
fuente
2

Aquí hay una solución que solo se desplaza a la parte superior del Componente si visita CADA componente por primera vez (en caso de que necesite hacer algo diferente por componente):

En cada componente:

export class MyComponent implements OnInit {

firstLoad: boolean = true;

...

ngOnInit() {

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

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

}

irretico
fuente
2

Angular 6.1 y posterior:

Puede usar una solución integrada disponible en Angular 6.1+ con la opción scrollPositionRestoration: 'enabled'de lograr lo mismo.

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

Angular 6.0 y versiones anteriores:

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);
            }
        });
    }
}

Nota: El comportamiento esperado es que cuando navega de regreso a la página, debe permanecer desplazada hacia abajo hasta la misma ubicación que estaba cuando hizo clic en el enlace, pero desplazándose hacia la parte superior al llegar a cada página.

s sharif
fuente
2

solo agrega

window.scrollTo({ top: 0);

para ngOnInit ()

Max Hesari
fuente
window.scroll (0,0)
Akitha_MJ
2

Para alguien que está buscando la función de desplazamiento, simplemente agregue la función y llame cuando sea necesario

scrollbarTop(){

  window.scroll(0,0);
}
Akitha_MJ
fuente
1

Prueba esto:

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
fuente
1

Componente: suscríbase a todos los eventos de enrutamiento en lugar de crear una acción en la plantilla y desplácese en NavigationEnd b / c; de lo contrario, lo activará en navegaciones incorrectas o rutas bloqueadas, etc. Esta es una forma segura de saber si se navega correctamente a una ruta, luego calme el desplazamiento. De lo contrario, no haga nada.

@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 Waggoner
fuente
1

prueba esto

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

este código soporta angular 6 <=

Rokive
fuente