73

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?

Francesco Borzi
  • 56,083
  • 47
  • 179
  • 252
Yegor
  • 1,088
  • 1
  • 10
  • 14
  • Possible duplicate of [How to detect a route change in Angular 2?](https://stackoverflow.com/questions/33520043/how-to-detect-a-route-change-in-angular-2) – Narottam Goyal Aug 20 '17 at 09:37

9 Answers9

154

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

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Do I need run `router.subscribe(...)` in every class or I can run them onetime? In docs I don't understand what params I must enter in subscribe function? Can you write full example, please? – Yegor Mar 10 '16 at 11:19
  • You can do it once in a global service and then inject the service wherever you want to get access. – Günter Zöchbauer Mar 10 '16 at 11:21
  • If I inject it as service in other components it doesn't return any data – Yegor Mar 10 '16 at 12:10
  • Can you please edit your question and add the code that demonstrates what you try to accomplish? I don't know where you inject the service to or how you do it. – Günter Zöchbauer Mar 10 '16 at 12:13
  • Instead of on the component (`providers: [SubscribeService]`) add `SubscribeService` only to `bootstrap(AppComponent, [OtherProviders, SubscribeService])` and nowhere else. – Günter Zöchbauer Mar 10 '16 at 12:49
  • Unfortunately, it's not working(( Bootstrap code: `other imports... import {SubscribeService} from './services/router.subscribe'; bootstrap(GlobalAppComponent, [ROUTER_PROVIDERS, provide(LocationStrategy, {useClass: HashLocationStrategy}), HTTP_PROVIDERS, SubscribeService]);` – Yegor Mar 10 '16 at 13:00
  • It seems that this runs multiple times. @GünterZöchbauer it worked for you? – Vassilis Pits Aug 30 '16 at 09:07
  • `subscribe()` means to get notified about every event therefore the callback passed to `subscribe(...)` is called every time an event is emitted.. One navigation can emit every event listed in my code. You might want to use the `filter()` operator to filter only a specific kind of event of just use `if(...)` as I did in my answer. – Günter Zöchbauer Aug 30 '16 at 09:09
  • @GünterZöchbauer I think Angular is mature enough to remove the way it was done pre-2.0.0 version (not 2.x, 4.x, 5.x differences). Both here and on any other answer I come across. Would you agree that it's good to edit the answer(s)? – Lazar Ljubenović Jan 11 '18 at 16:45
  • 4
    @LazarLjubenović people often find old examples and then run into issues because the code doesn't work anymore. I think for them it's helpful to see that this is because Angular changed and that exmple is for an older Angular version. There is no other way to learn that, because old docs aren't available anymore. – Günter Zöchbauer Jan 11 '18 at 17:52
12

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.

enter image description here

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
6

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...
  }
});
Khaled Al-Ansari
  • 3,910
  • 2
  • 24
  • 27
4

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.

danday74
  • 52,471
  • 49
  • 232
  • 283
2
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)
    }
  });
}
San Jaisy
  • 15,327
  • 34
  • 171
  • 290
0

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
  }
}
Y. Brahimi
  • 63
  • 2
  • 7
0

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

0

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

Shy Agam
  • 1,285
  • 1
  • 13
  • 37
-1

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/