Scroll to top on Angular Router navigation
Posted on
Last update on
Update (December 2018): This article has been updated to represent the newly available ViewportScroller
class, available from Angular v7+. This class implementation wraps around the window object and only executes if the window object is available.
Update (July 2019): This article has been updated to use the proper configuration on the RouterModule
to perform a scroll to top on navigation and preserve the previous scroll position. See the last section of this article.
When I was creating this blog and optimising it for mobile I experienced some default but not so user-friendly behaviour when navigating from one route to the other with Angular.
The problem is that content on mobile can go very deep below the initial viewport height. So when you're scrolling down and you press an internal link to another page, you'll be stuck at that height.
This is somewhat different from standard navigation between pages in a normal web-application, where the page reloads and you start from the top by default. In a S.P.A. this can easily be solved by scrolling to the top on navigation by using the native window.scroll
function:
window.scroll(0,0)
A navigation in routing in Angular 1 and ngRoute or even the ui-router can easily be detected by listening to the event $routeChangeSuccess
or $stateChangeSuccess
. So combining these 2 essentials gives us:
angular-js-route-state-change.ts
// ngRoute:
$rootScope.$on('$routeChangeSuccess', () => {
$window.scroll(0,0);
});
// ui-router:
$rootScope.$on('$stateChangeSuccess', () => {
$window.scroll(0,0);
});
I did not find anything similar in the documentation of the router of Angular so I went digging. The fact is that I'm using the Angulartics2 plugin by @luisfarzati to track you guys' behavior :). This is also done on navigation so there must be something similar going on at that plugin. The plugin BTW works great!
Listening to navigation events in Angular
It seems that the Angular v2+ router has an events
Observable property which you can subscribe on. Yes, it is as simple as that. Those events can be of any predefined type NavigationStart
, NavigationCancel
, NavigationEnd
or NavigationError
. In my case I only needed the NavigationEnd
.
In the component that holds your navigation router-outlet
you just need to setup the listener, something like this:
app.component.ts
import { Component } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { ViewportScroller } from '@angular/common';
@Component({
selector: 'sv-app',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(
readonly router: Router,
readonly viewportScroller: ViewportScroller
) {
router.events
.filter(event => event instanceof NavigationEnd)
.subscribe((event: NavigationEnd) => {
// Angular v2-v6
window.scroll(0, 0);
// Angular v7+
this.viewportScroller.scrollToPosition([0, 0]);
});
}
}
Update (December 2018): This article has been updated to represent the newly available ViewportScroller
class, available from Angular v7+. This class implementation wraps around the window object and only executes if the window object is available.
Update (July 2019): This article has been updated to use the proper configuration on the RouterModule
to perform a scroll to top on navigation and preserve the previous scroll position.
Using the scrollPositionRestoration
config option of the RouterModule
Since v6.1
it is possible to set a configuration option scrollPositionRestoration
on the RouterModule
that will preserve the scrolling position of the previous route and scroll to top on a succesful navigation to the new route.
app-routin.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: '',
loadChildren: './pages/home/home.module#HomeModule',
},
// ..
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
initialNavigation: 'enabled',
scrollPositionRestoration: 'enabled'
})
],
exports: [RouterModule],
})
export class AppRoutingModule {
}
This is currently implemented on my blog as you can see. And with a simple style setting on the HTML this performs a smooth scroll whenever there is the need to!
styles.scss
html {
scroll-behavior: smooth;
}
Visual example
In the visual example below I first scroll down on the projects page. Next I navigate to the posts overview. During this navigation, the window is scrolled to top automatically. When I hit back in the browser, the application is again focussed on the scrolling position I was before.
Conclusion
And that was it! Be aware that the window
object might not be available in every context, except the browser. Check this awesome article by @juristr to read more about why you might want to wrap your window
object reference!
Please also be careful not to use these events to do business logic, like for example checking if you can navigate to a specific route based on some authentication rules. For those cases you may want to implement guards! More information about guards can be found in this splendid article by @PascalPrecht of Thoughtram.
By reading this article I hope you can find a solution for your problem. If it still seems a little bit unclear, you can hire me for helping you solve your specific problem or use case. Sometimes even just a quick code review or second opinion can make a great difference.