ChatGPT解决这个技术问题 Extra ChatGPT

Angular 5 Scroll to top on every Route click

I am using Angular 5. I have a dashboard where I have few sections with small content and few sections with so large content that I am facing a problem when changing router while going to top. Every time I need to scroll to go to top.

How can I solve this issue so that when I change the router, my view always stay at the top?


V
Vega

There are some solutions, make sure to check them all :)

Option1:

The router outlet will emit the activate event any time a new component is being instantiated, so we could use (activate) to scroll (for example) to the top:

app.component.html

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

app.component.ts

onActivate(event) {
   // window.scroll(0,0);

   window.scroll({ 
           top: 0, 
           left: 0, 
           behavior: 'smooth' 
    });

    //or document.body.scrollTop = 0;
    //or document.querySelector('body').scrollTo(0,0)
    ...
}

As the smooth scroll is not implemented well in Safari, use, for exemple, this solution for a smooth scroll:

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

If you wish to be selective, say not every component should trigger the scrolling, you can check it in an if statement like this:

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

Option2:

Since Angular6.1, we can also use { scrollPositionRestoration: 'enabled' } on eagerly loaded modules and it will be applied to all routes:

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

It will also do the smooth scrolling, already. However this has the inconvenient for doing it on every routing.

Option3:

An other solution is to do the top scrolling on router animation. Add this in every transition where you want to scroll to the top:

query(':enter, :leave', style({ position: 'fixed' }), { optional: true }) 

scroll events on window object are not working in angular 5. Any guesses why?
@SahilBabbar, Check the body css, overflow:hiden ? what is its height?
@Vega no. The height of the body is default and nothing is hard coded inside, as it is a normal Angular 5 application. Moreover take a look at Angular docs where they say that scroll events are blacklisted by ngzones.
Try document.body.scrollTop = 0; or with old js document.querySelector('body').scrollTo(0,0), etc.. If those don't work provide an MCVE
Is there any way for lazyloaded module?
s
s.alem

If you face this problem in Angular 6, you can fix it by adding the parameter scrollPositionRestoration: 'enabled' to app-routing.module.ts 's RouterModule:

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

Note that as of 6-Nov-2019 using Angular 8, at least, the scrollPositionRestoration property does not work with dynamic page content (i.e., where the page content is loaded asynchronously): see this Angular bug report: github.com/angular/angular/issues/24547
G
GeoRover

EDIT: For Angular 6+, please use Nimesh Nishara Indimagedara's answer mentioning:

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

Original Answer:

If all fails, then create some empty HTML element (eg: div) at the top (or desired scroll to location) with id="top" on template (or parent template):

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

And in component:

  ngAfterViewInit() {
    // Hack: Scrolls to top of Page after page view initialized
    let top = document.getElementById('top');
    if (top !== null) {
      top.scrollIntoView();
      top = null;
    }
  }

This solution worked for me (tested on Chrome as well as Edge). The accepted solution didn't work for my project (Angular5)
@RobvanMeeuwen, If my answer didn't work it probably you did not implement it the same way. This solution is manipulating directely the DOM which is not correct either safe
@Vega, that's why I called it a hack. Your solution is the proper one. Some folks here were not able to implement yours so I offered the fallback hack. They should refactor their code based on the version they are on at this point.
From all this solution is work for me. Thanks @GeoRover
For Angular 6+, please use Nimesh Nishara Indimagedara's answer.
T
Tim Diekmann

Now there's a built in solution available in Angular 6.1 with scrollPositionRestoration option.

See my answer on Angular 2 Scroll to top on Route Change.


V
Vikas

From Angular Version 6+ No need to use window.scroll(0,0)

For Angular version 6+ from @docs
Represents options to configure the router.

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

One can use scrollPositionRestoration?: 'disabled' | 'enabled' | 'top' in

Example:

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

And if one requires to manually control the scrolling, No need to use window.scroll(0,0) Instead from Angular V6 common package has introduced 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
}

Usage is pretty Straightforward Example:

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

Weirdly, every solution works in some cases and fails in others. Angular scrolling to top is seriously flawed.
I was able to inject ViewportScroller into my component. It woked.
S
Sal_Vader_808

Although @Vega provides the direct answer to your question, there are issues. It breaks the browser's back/forward button. If you're user clicks the browser back or forward button, they lose their place and gets scrolled way at the top. This can be a bit of a pain for your users if they had to scroll way down to get to a link and decided to click back only to find the scrollbar had been reset to the top.

Here's my solution to the problem.

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

Thank you for advanced code and nice solution. But sometimes @Vega solution is better, because it solve many problems with animation and dynamic page height. You solution is good if you have long page with content and simple routing animation. I try it on page with many animation and dynamics block and it's looks not so good. I think sometimes we can sacrifice 'back position' for our app. But if not - you solution is the best what I see for Angular. Thank you again
Z
Zohab Ali

In my case I just added

window.scroll(0,0);

in ngOnInit() and its working fine.


s
s sharif

Angular 6.1 and later:

You can use built in solution available in Angular 6.1+ with option scrollPositionRestoration: 'enabled' to achieve the same.

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

Angular 6.0 and earlier:

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

Note: The expected behavior is that when you navigate back to the page, it should remain scrolled down to the same location it was when you clicked on the link, but scrolling to the top when arriving at every page.


i
ivin antony

if your using mat-sidenav give an id to the router outlet( if you have a parent and child router outlets) and use activate function in it <router-outlet id="main-content" (activate)="onActivate($event)"> and use this 'mat-sidenav-content' query selector to scroll top onActivate(event) { document.querySelector("mat-sidenav-content").scrollTo(0, 0); }


Works great even without using an id (I have a single router-outlet on my app). I also did it on a more 'angular way': @ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer; onActivate() { this.sidenavContainer.scrollable.scrollTo({ left: 0, top: 0 }); }
A
Akitha_MJ

For some one who is looking for scroll function just add the function and call when ever needed

scrollbarTop(){

  window.scroll(0,0);
}

J
Jared Whipple

I keep looking for a built in solution to this problem like there is in AngularJS. But until then this solution works for me, It's simple, and preserves back button functionality.

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

Answer from zurfyx original post


V
Vijay Barot

You just need to create a function which contains adjustment of scrolling of your screen

for example

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

window.scrollTo(xpos, ypos) --> expected parameter.


R
Ria Pacheco

None of the above worked for me for some reason :/, so I added an element ref to a top element in app.component.html, and (activate)=onNavigate($event) to the router-outlet.

<!--app.component.html-->
<div #topScrollAnchor></div>
<app-navbar></app-navbar>
<router-outlet (activate)="onNavigate($event)"></router-outlet>

Then I added the child to the app.component.ts file to the type of ElementRef, and had it scroll to it on activation of the router-outlet.

export class AppComponent  {
  @ViewChild('topScrollAnchor') topScroll: ElementRef;

  onNavigate(event): any {
    this.topScroll.nativeElement.scrollIntoView({ behavior: 'smooth' });
  }
}

Here's the code in stackblitz


This was the only one working for me as well. I am using ion-split-pane, and window.scroll doesn't seem to work for it.
B
Breno Gomes

The solution that worked for me:

document.getElementsByClassName('layout-content')[0].scrollTo(0, 0);

It's worked in angular 8, 9 and 10.


scrollTo(0, 0) did'nt work for me, but scrollIntoView() did. Thanks anyway!
A
Anis KCHAOU

Just add this line on app.module.ts file :

RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled' //scroll to the top
})

I'm using Angular 11.1.4 and its worked for me


T
Tsvetan Ganev

just add

window.scrollTo({ top: 0 });

to ngOnInit()


window.scroll(0,0)
0
0_tr0jan_0

Here is a solution that only scrolls to top of Component if first time visiting for EACH component (in case you need to do something different per component):

In each Component:

export class MyComponent implements OnInit {

firstLoad: boolean = true;

...

ngOnInit() {

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

E
Eray T

Try this:

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

i
irhetoric

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


J
Joshua Michael Calafell

Component: Subscribe to all routing events instead of creating an action in the template and scroll on NavigationEnd b/c otherwise you'll fire this off on bad navs or blocked routes, etc... This is a sure fire way to know that if a route successfully is navigated to, then sooth scroll. Otherwise, do nothing.

@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>

R
Rokive

try this

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

this code supported angular 6<=


K
Kareem Adel

Just Add

 ngAfterViewInit() {
  window.scroll(0,0)
 }