Angular 5 Role para cima em cada clique de rota

99

Estou usando o angular 5. Tenho um painel onde tenho poucas seções com conteúdo pequeno e poucas seções com conteúdo tão grande que estou enfrentando um problema ao mudar de roteador enquanto vou para o topo. Sempre que preciso rolar para ir para o topo. Alguém pode me ajudar a resolver esse problema para que quando eu trocar de roteador minha visão fique sempre no topo.

Desde já, obrigado.

Raihan
fonte
Possível duplicata de Angular 2 Role para cima na Mudança de rota
Cerbrus

Respostas:

213

Existem algumas soluções, certifique-se de verificar todas :)


A saída do roteador emitirá o evento `activate` sempre que um novo componente estiver sendo instanciado, então podemos usar` (activate) `para rolar (por exemplo) para o topo:

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

Use esta resposta para uma rolagem 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);
    }

Se você deseja ser seletivo, digamos que nem todos os componentes devam acionar a rolagem, você pode verificar, então:

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

Desde o Angular6.1, também podemos usar `{scrollPositionRestoration: 'enabled'}` em módulos carregados rapidamente e será aplicado a todas as rotas:
RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled' })

Ele também fará a rolagem suave já. No entanto, isso tem o inconveniente de fazer isso em todos os roteamentos.


Uma outra solução é fazer a rolagem superior na animação do roteador. Adicione isso em cada transição em que deseja rolar para o topo:
query(':enter, :leave', style({ position: 'fixed' }), { optional: true }) 
Vega
fonte
4
está funcionando . Obrigado pela sua resposta. você economiza muito tempo para mim
raihan
2
eventos de rolagem no windowobjeto não estão funcionando no angular 5. Algum palpite por quê?
Sahil Babbar
2
Um verdadeiro herói aqui. Economizei possíveis horas de frustração. Obrigado!
Anjana Silva
1
@AnjanaSilva, obrigada pelo comentário positivo! Estou muito feliz que isso possa ajudá-lo :)
Vega
2
Existe alguma maneira de módulo lazyloaded?
Pankaj Prakash
54

Se você enfrentar esse problema no Angular 6, poderá corrigi-lo adicionando o parâmetro scrollPositionRestoration: 'enabled'ao RouterModule do app-routing.module.ts:

@NgModule({
  imports: [RouterModule.forRoot(routes,{
    scrollPositionRestoration: 'enabled'
  })],
  exports: [RouterModule]
})
Nimezzz
fonte
7
Por favor, não poste imagens de código. Basta copiar e colar o próprio código em sua resposta.
mypetlion
2
Certo. Da próxima vez eu vou. Eu sou meio novo aqui. :)
Nimezzz
4
Observe que a partir de 6 de novembro de 2019 usando Angular 8, pelo menos, a scrollPositionRestorationpropriedade não funciona com conteúdo de página dinâmico (ou seja, onde o conteúdo da página é carregado de forma assíncrona): consulte este relatório de bug Angular: github.com/angular/angular /
Issues
25

EDITAR: Para Angular 6+, use a resposta de Nimesh Nishara Indimagedara mencionando:

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

Resposta Original:

Se tudo falhar, crie algum elemento HTML vazio (por exemplo: div) na parte superior (ou role até o local desejado) com id = "top" no modelo (ou modelo pai):

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

E no 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
fonte
2
Esta solução funcionou para mim (testada no Chrome e também no Edge). A solução aceita não funcionou para meu projeto (Angular5)
Rob van Meeuwen
@RobvanMeeuwen, Se minha resposta não funcionou, provavelmente você não a implementou da mesma forma. Esta solução está manipulando diretamente o DOM, o que também não é correto e seguro
Vega
@Vega, é por isso que chamei de hack. Sua solução é a correta. Algumas pessoas aqui não foram capazes de implementar o seu, então ofereci o hack substituto. Eles devem refatorar seu código com base na versão em que estão neste momento.
GeoRover
De toda essa solução é trabalho para mim. Obrigado @GeoRover
Gangani Roshan
Para Angular 6+, use a resposta de Nimesh Nishara Indimagedara.
GeoRover
6

Embora @Vega forneça uma resposta direta à sua pergunta, existem problemas. Ele quebra o botão voltar / avançar do navegador. Se você clicar no botão voltar ou avançar do navegador, eles perderão seu lugar e serão rolados para a parte superior. Isso pode ser um pouco chato para seus usuários se eles tiverem que rolar para baixo para acessar um link e decidir clicar novamente para descobrir que a barra de rolagem foi redefinida para o topo.

Aqui está minha solução para o 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
fonte
2
Obrigado pelo código avançado e boa solução. Mas às vezes a solução @Vega é melhor, porque resolve muitos problemas com animação e altura de página dinâmica. Sua solução é boa se você tiver uma página longa com conteúdo e animação de roteamento simples. Eu tentei na página com muitos blocos de animação e dinâmica e não parece tão bom. Acho que às vezes podemos sacrificar a 'posição traseira' para nosso aplicativo. Mas se não - sua solução é a melhor que vejo para o Angular. Obrigado novamente
Denis Savenko,
5

No meu caso, acabei de adicionar

window.scroll(0,0);

em ngOnInit()e está funcionando bem.

Zohab Ali
fonte
4

Da versão Angular 6+ Não há necessidade de usar window.scroll (0,0)

Para a versão Angular 6+de @ representa as opções de configuração do roteador.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'
}

Pode-se usar scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'em

Exemplo:

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

E se for necessário controlar manualmente a rolagem, não há necessidade de usar o window.scroll(0,0) pacote comum do 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
}

O uso é bastante simples. Exemplo:

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

se você estiver usando mat-sidenav, forneça um id para a saída do roteador (se você tiver saídas de roteador pai e filho) e use a função ativar nela <router-outlet id="main-content" (activate)="onActivate($event)"> e use este seletor de consulta 'mat-sidenav-content' para rolar para cima onActivate(event) { document.querySelector("mat-sidenav-content").scrollTo(0, 0); }

ivin antony
fonte
Funciona muito bem mesmo sem usar um id (tenho um router-outletno meu aplicativo). Eu também fiz isso de uma forma mais 'angular': @ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer; onActivate() { this.sidenavContainer.scrollable.scrollTo({ left: 0, top: 0 }); }
GCSDC
3

Eu continuo procurando uma solução embutida para este problema como existe no AngularJS. Mas até então essa solução funciona para mim, é simples e preserva a funcionalidade do botão Voltar.

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

Resposta da postagem original do zurfyx

Jared Whipple
fonte
3

Você só precisa criar uma função que contenha o ajuste de rolagem da tela

por exemplo

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

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

Vijay Barot
fonte
2

Aqui está uma solução que só rola para o topo do componente se você visitar pela primeira vez CADA componente (no caso de você precisar fazer algo diferente por componente):

Em cada componente:

export class MyComponent implements OnInit {

firstLoad: boolean = true;

...

ngOnInit() {

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

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

}

iretórica
fonte
2

Angular 6.1 e posterior:

Você pode usar a solução integrada disponível no Angular 6.1+ com a opção de scrollPositionRestoration: 'enabled'fazer o mesmo.

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

Angular 6.0 e anterior:

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

Observação: o comportamento esperado é que, ao navegar de volta para a página, ela deve permanecer rolada para baixo até o mesmo local em que estava quando você clicou no link, mas rolando para o topo ao chegar a cada página.

s sharif
fonte
2

apenas adicione

window.scrollTo({ top: 0);

para ngOnInit ()

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

Para alguém que está procurando a função de rolagem, basta adicionar a função e chamar sempre que necessário

scrollbarTop(){

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

Experimente isto:

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

Componente: Inscreva-se em todos os eventos de roteamento em vez de criar uma ação no modelo e role em NavigationEnd b / c, caso contrário, você o disparará em navs ruins ou rotas bloqueadas, etc ... Esta é uma maneira segura de saber se uma rota é navegada com sucesso e, em seguida, role a tela. Caso contrário, não faça 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 Wagoner
fonte
1

tente isso

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

este código suportava angular 6 <=

Rokive
fonte