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?
Solution 1:
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);
}
});
}
}
Solution 2:
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)
});
}
}
Solution 3:
From Angular 6.1, you can now avoid the hassle and pass extraOptions
to your RouterModule.forRoot()
as a second parameter and can specify scrollPositionRestoration: enabled
to tell Angular to scroll to top whenever the route changes.
By default you will find this in app-routing.module.ts
:
const routes: Routes = [
{
path: '...'
component: ...
},
...
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled', // Add options right here
})
],
exports: [RouterModule]
})
export class AppRoutingModule { }
Angular Official Docs