Originally published at https://samvloeberghs.be on November 19, 2016
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.
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:
// 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:
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.
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.
Originally published at https://samvloeberghs.be on November 19, 2016