Angular 2 router event listener
JavascriptAngularTypescriptAngular2 RoutingJavascript Problem Overview
How to listen state change in Angular 2 router?
In Angular 1.x I used this event:
$rootScope.$on('$stateChangeStart',
function(event,toState,toParams,fromState,fromParams, options){ ... })
So, if I use this eventlistener in Angular 2:
window.addEventListener("hashchange", () => {return console.log('ok')}, false);
it isn't return 'ok', then change state from JS, only then browser history.back() function run.
Use router.subscribe() function as the service:
import {Injectable} from 'angular2/core';
import {Router} from 'angular2/router';
@Injectable()
export class SubscribeService {
constructor (private _router: Router) {
this._router.subscribe(val => {
console.info(val, '<-- subscribe func');
})
}
}
Inject service in component which init in routing:
import {Component} from 'angular2/core';
import {Router} from 'angular2/router';
@Component({
selector: 'main',
templateUrl: '../templates/main.html',
providers: [SubscribeService]
})
export class MainComponent {
constructor (private subscribeService: SubscribeService) {}
}
I inject this service in other components such as in this example. Then I change state, console.info() in service not working.
What I do wrong?
Javascript Solutions
Solution 1 - Javascript
new router
constructor(router:Router) {
router.events.subscribe(event:Event => {
if(event instanceof NavigationStart) {
}
// NavigationEnd
// NavigationCancel
// NavigationError
// RoutesRecognized
});
}
old
Inject the Router and subscribe to route change events
import {Router} from 'angular2/router';
class MyComponent {
constructor(router:Router) {
router.subscribe(...)
}
}
NOTE
For the new router, don't forget to import NavigationStart
from router
module
import { Router, NavigationStart } from '@angular/router';
because if you don't import it instanceof
will not work and an error NavigationStart is not defined
will rise.
See also
Solution 2 - Javascript
You can also filter events with filter()
.
But don't just use filter(e => e is NavigationEnd)
A much better solution is to add a 'type guard' to filter()
like this:
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
It contains two things:
e is NavigationEnd
this is the assertion you're defining a function for (this is typescript syntax and is completely stripped out of the transpiled javascript)e instanceof NavigationEnd
this is the actual runtime code that checks the type
The nice thing with this is that operators further down 'the pipe', like map
below now know the type is NavigationEnd
, but without the type-guard you'd have a type Event
.
If you only need to check for one event type then this is the cleanest way to do so. This also appears to be necessary in strict mode to avoid compiler errors.
Solution 3 - Javascript
You can use instanceof
as @GünterZöchbauer answered
this.router.events.subscribe(event => {
if(event instanceof NavigationStart) {
// do something...
}
}
or you can use a lazier approach, but remember constructor name can be changed easily while the function is still working!
this.router.events.subscribe(event => {
if(event.constructor.name === "NavigationStart") {
// do something...
}
});
Solution 4 - Javascript
Straight from the docs
import {Event, RouterEvent, Router, NavigationEnd} from '@angular/router';
this.router.events.pipe(
filter((e: any): e is RouterEvent => e instanceof RouterEvent)
).subscribe((evt: RouterEvent) => {
if (evt instanceof NavigationEnd) {
console.log(evt.url)
}
})
Although the docs give the code filter((e: Event)
but I changed this to filter((e: any)
or you get linting errors in WebStorm.
Solution 5 - Javascript
import { Router,NavigationEnd } from '@angular/router';
constructor(private route:Router){
this.routeEvent(this.route);
}
routeEvent(router: Router){
router.events.subscribe(e => {
if(e instanceof NavigationEnd){
console.log(e)
}
});
}
Solution 6 - Javascript
The angular 2 router events has different classes, and what gets passed to the subscription from the router.events
observable can either be NavigationEnd
, NavigationCancel
, NavigationError
, or NavigationStart
. The one that will actually trigger a routing update will be NavigationEnd
.
I would stay away from using instanceof
or event.constructor.name
because after minification the class names will get mangled it will not work correctly.
You can use the router's isActive
function instead, shown here https://angular.io/docs/ts/latest/api/router/index/Router-class.html
this.routerEventSubscription = this._router.events.subscribe((event: any) => {
if (this._router.isActive(events.url, false)) {
// true if the url route is active
}
}
Solution 7 - Javascript
in angular2, go to file "app.modules.ts"->imports
RouterModule.forRoot(
appRoutes,
{
enableTracing: true
}
)
in enableTracing true show routeEvents in console in enableTracing false hide routeEvents in console
Solution 8 - Javascript
With @bespunky/angular-zen this has become a lot simpler...
Basically, extend the RouteAware
class and create an on<EventType>()
method:
import { Component } from '@angular/core';
import { NavigationStart, NavigationEnd, RoutesRecognized } from '@angular/router';
import { RouteAware } from '@bespunky/angular-zen/router-x';
@Component({
selector : 'app-demo',
templateUrl: './demo.component.html',
styleUrls : ['./demo.component.css']
})
export class DemoComponent extends RouteAware
{
// ✨ Any router event can have a handler method.
// See https://angular.io/guide/router#router-events for a complete list of angular's router events.
// ✨ Use `this.router` to access the router
// ✨ Use `this.route` to access the activated route
// ✨ Use `this.componentBus` to access the RouterOutletComponentBus service
protected onNavigationStart(event: NavigationStart): void
{
console.log(`Navigation started for: ${event.url}`);
}
protected onRoutesRecognized(event: RoutesRecognized): void
{
console.log('Recognized routes.');
}
protected onNavigationEnd(event: NavigationEnd): void
{
console.log(`Navigation ended for: ${event.url}`);
}
}
Take a look at this answer: https://stackoverflow.com/a/64864103/4371525
Solution 9 - Javascript
To listen to all state changes, extend the default RouterOutlet and add your own logic in 'activate' and 'deactivate' handlers.
import {Directive} from 'angular2/core';
import {Router, RouterOutlet, ComponentInstruction} from 'angular2/router';
@Directive({
selector: 'router-outlet'
})
export class MyOwnRouterOutlet extends RouterOutlet {
...
activate() {
console.log('Hello from the new router outlet!');
}
}
Copied from 'Custom Router Outlet' example here: https://auth0.com/blog/2016/01/25/angular-2-series-part-4-component-router-in-depth/