-->

Angular 2 router event not firing first time?

2020-08-09 05:32发布

问题:

I am routing from one component to another. Once the route is done I would like to use the URL from the previous route. I have put the code below into the constuctor of the component being routed to, but it does not fire on the first route. After the first route, the function fires every time.

this.router.events
      .filter(e => e instanceof NavigationEnd)
      .pairwise().subscribe((e) => {
          console.log(e);
    });

If I remove the pairwise function it seems to fire on first route, however it lists only the current route, not the previous route.

 router.events
  .filter(e => e instanceof NavigationEnd)
  .subscribe(e => {
    console.log(e);
 });

My goal is to retrieve the previous route when the new component is routed to. What am I doing wrong here?

回答1:

I had exactly the same scenario and I found out that it's too late to subscribe for NavigationEnd with pairwise in the constructor of the child component.

You could subscribe to the router in your root component and share the route data through a service like the following example:

events.service.ts

import { Injectable } from '@angular/core';
import { RouteChanged } from '../models/route-changed.model';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class EventsService {
  public routeChanged = new BehaviorSubject<RouteChanged>({ prevRoute: '/', currentRoute: '/' });

  constructor() {
  }
}

app.component.ts (your root component)

...

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
    private subscriptions = new Subscription();

    constructor(private eventsService: EventsService) {
            this.subscriptions.add(this.router.events
                .filter(event => event instanceof NavigationEnd)
                .pairwise()
                .subscribe(navigationEvents => {
                    const prevPage = <NavigationEnd>navigationEvents[0];
                    const currentPage = <NavigationEnd>navigationEvents[1];
                    this.eventsService.routeChanged.next(
                        { prevRoute: prevPage.urlAfterRedirects, currentRoute: currentPage.urlAfterRedirects });
                }));
        }

    ngOnDestroy(): void {
        this.subscriptions.unsubscribe();
    }

    ...
}

your-target-route.ts

...
constructor(private eventsService: EventsService) {
    this.subscriptions.add(this.eventsService.routeChanged.subscribe((routeChanged) => {
        // use routeChanged.prevRoute and routeChanged.currentRoute here...
    }));
}

P.S. It's important to use a BehaviorSubject or a ReplaySubject in the service in order to get the proper previous route event if your child component subscribes after the page load.



回答2:

The answer was already given: The NavigationEnd event has already been raised when the component registers for it. I don't like the idea of "Dimitar Tachev" to create a workaround by proxying those events through a subject. In my case the solution was to:

  1. Let the Component subscribe to the NavigationEnd event as before.
  2. Make the Component load the initial state from the injected Route object in the ngOnInit method.

And finally another solution is to move the subscription to the route change events into the constructor of the component.



回答3:

pairwise emits on the second and subsequent emissions (http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-pairwise)

so it's important to set initial value for example:

this.router.events
  .startWith(new NavigationEnd(0, '/', '/'))
  .filter(e => e instanceof NavigationEnd)
  .pairwise()
  .subscribe((e) => {
      console.log(e);
  });


回答4:

2019, this is still an issue

Here's how to get all route changes including the page load route in a sub-component.

  import { filter } from 'rxjs/operators';

  constructor(
    private router: Router, 
    private route: ActivatedRoute
  ) {
    // First route event on load
    this.setFromSnapshot(this.route.snapshot);
    // Subsequent events after load
    this.router.events
      .pipe(filter(event => event instanceof ActivationStart))
      .subscribe((event:ActivationStart) => this.setFromSnapshot(event.snapshot));
  }

  setFromSnapshot(snapshot:ActivatedRouteSnapshot) {
    // Do something
  }



回答5:

The reason is that your component doesn't yet exist when the navigation event occurs.

Create a global service injected in app.component.ts and inject it into your component too.

Create 'helper' observables on the service.

// RouterEventsService.ts 
navigationStart$ = this.router.events.pipe(filter((e): e is NavigationStart => e instanceof NavigationStart));
navigationEnd$ = this.router.events.pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd));

navigationEndReplay$ = this.navigationEnd$.pipe(shareReplay(1));

In your constructor for the service you must subscribe to only the navigationEndReplay$ event. This means that later in your component you can subscribe to it and get the cached most recent end event.

// RouterEventsService.ts constructor
this.routerEvents.navigationEndReplay$.subscribe();   // note: unsubscribe in app.component not needed

Then you can use routerEvents.navigationEndReplay$ in your component and it will trigger immediately once the component is initialized. You can use it in your constructor, ngOnInit, ngAfterContentInit etc.

Best to keep both navigationEnd$ and navigationEndReplay$ to be explicit.



回答6:

Inspired by the previous answers I made the following solution:

  import { filter, map, startWith } from 'rxjs/operators';

  constructor(
    private router: Router, 
    private route: ActivatedRoute
  ) {
    this.router.events
      .pipe(
        filter(event => event instanceof ActivationStart),
        map(({ snapshot }) => snapshot),
        startWith(this.route.snapshot)
      )
      .subscribe((snapshot: ActivatedRouteSnapshot) => /** ... do something */ )
  }


回答7:

As I see all these solutions relay to route.snapshot and try to combine NavigationEnd action with it. But if you try to access the page - it is not fired with the first NavigationEnd by router.events. Also snapshot fires parent route, not a child route. so first you could use child route, but it will be also not fired as first...

this.router.events.pipe(
  filter((event) => event instanceof NavigationEnd),
  map(() => this.aRoute.snapshot),
  map((route) => {
    while (route.firstChild) {
      route = route.firstChild;
    }
    return route;
  })
)

so BEST solution for me with first fire on proper current route is using router.events + router.url with one type NavigationEnd:

 this.router.events
   .pipe(
      filter((event) => event instanceof NavigationEnd),
      startWith(this.router)
   )
   .subscribe((event: NavigationEnd) => {
     // there will be first router.url - and next - router events
 })


回答8:

I just stumbled over the same problem and found the reason for it: The Service subscribing to the Router events was never instanciated by the Dependency Injector, because the Service was not injected at that route.

A Service seems to be only instanciated when it is injected somewhere.

So check your first route if the whole code (not the event) is ever called.