Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 Scroll to top on Route Change

In my Angular 2 app when I scroll down a page and click the link at the bottom of the page, it does change the route and takes me to the next page but it doesn't scroll to the top of the page. As a result, if the first page is lengthy and 2nd page has few contents, it gives an impression that the 2nd page lacks the contents. Since the contents are visible only if a user scrolls to the top of the page.

I can scroll the window to the top of the page in ngInit of the component but, is there any better solution that can automatically handle all routes in my app?

like image 889
Naveed Ahmed Avatar asked Sep 20 '16 18:09

Naveed Ahmed


2 Answers

Angular 6.1 and later:

Angular 6.1 (released on 2018-07-25) added built-in support to handle this issue, through a feature called "Router Scroll Position Restoration". As described in the official Angular blog, you just need to enable this in the router configuration like this:

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

Furthermore, the blog states "It is expected that this will become the default in a future major release". So far this hasn't happened (as of Angular 11.0), but eventually you won't need to do anything at all in your code, and this will just work correctly out of the box.

You can see more details about this feature and how to customize this behavior in the official docs.

Angular 6.0 and earlier:

While @GuilhermeMeireles's excellent answer fixes the original problem, it introduces a new one, by breaking the normal behavior you expect when you navigate back or forward (with browser buttons or via Location in code). 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 obviously breaks this expectation.

The code below expands the logic to detect this kind of navigation by subscribing to Location's PopStateEvent sequence and skipping the scroll-to-top logic if the newly arrived-at page is a result of such an event.

If the page you navigate back from is long enough to cover the whole viewport, the scroll position is restored automatically, but as @JordanNelson correctly pointed out, if the page is shorter you need to keep track of the original y scroll position and restored it explicitly when you go back to the page. The updated version of the code covers this case too, by always explicitly restoring the scroll position.

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);             }         });     } } 
like image 107
Fernando Echeverria Avatar answered Sep 23 '22 01:09

Fernando Echeverria


You can register a route change listener on your main component and scroll to top on route changes.

import { Component, OnInit } from '@angular/core'; import { Router, NavigationEnd } from '@angular/router';  @Component({     selector: 'my-app',     template: '<ng-content></ng-content>', }) export class MyAppComponent implements OnInit {     constructor(private router: Router) { }      ngOnInit() {         this.router.events.subscribe((evt) => {             if (!(evt instanceof NavigationEnd)) {                 return;             }             window.scrollTo(0, 0)         });     } } 
like image 43
Guilherme Meireles Avatar answered Sep 22 '22 01:09

Guilherme Meireles