The new Angular Router is powerful, relying on observables, pattern matching, conventions and customizations (Resolves or Guards) but it is an evolving project that doesn't yet cater to every use case.
If your Angular application uses the router then your components have access to all the routing information that led to them being initialized like the url, tokenized parameters, query string values and url fragments. This information is made available through the Router, ActivatedRoute and ActivatedRouteSnapshot services.
For example if you have a route defined as
'users/:user_id/accounts'
and the url you navigate to in
your app is /users/53/accounts
then you can access the id of the
user by injecting the ActivatedRouteSnapshot service into your component and
accessing activatedRouteSnapshot.params['user_id']
.
Unfortunately as soon as you start using child routes you lose access to any parameters defined in the part of the route that loads the parent component and likewise for parent components not being able to access child route parameters.
Let's look at this example route definition
const routes: Routes = [
{
path: 'global-users/access/:user_id',
component: UserAccessComponent,
canActivate: [AuthGuard],
},
{
path: 'users/:user_id/accounts',
component: UserRootComponent,
canActivateChild: [AuthGuard],
resolve: [InvitationServiceResolve],
children: [
{ path: ':account_id', component: AccountDetailComponent },
{ path: '', component: AccountListComponent }
]
}
];
If you have a route structure like this you've possibly run into the
problem I described above. The UserRootComponent
has access to
user_id
but not account_id
and the
AccountDetailComponent
has access to the
account_id
but not the user_id
.
My solution has been to use a global service and an observable pipeline in my
root AppComponent
.
Below is the RouteHelper
service which exposes an observable
params
that any component can have access to by injecting the
RouteHelper
. The params
observable is a stream of
objects with keys matching the route tokens and values matching those live url
values.
// './shared/route-helper.service'
import { Injectable } from '@angular/core';
import { Params } from '@angular/router';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class RouterHelper {
params: Observable;
private paramsSource: BehaviorSubject;
constructor() {
this.paramsSource = new BehaviorSubject({});
this.params = this.paramsSource.asObservable();
}
setParams(params: Params) {
this.paramsSource.next(params);
}
}
And below is the observable pipeline which delivers global route data to the RouteHelper service.
First we watch all router events, filtering for only completed navitations.
These are then mapped to the ActivatedRoute
which is used to
build up an array of observables of the Param
objects of each
route segment going from the root to the furthest child. Then using
combineLatest
and mergeMap
we go from an array of
observables of Params
to an array of Params
which we
can reduce to an object with keys of each parameter token and values of those
token's values. This object is then sent to the
RouteHelper
service.
// './AppComponent.ts'
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Params, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { RouterHelper } from './shared/shared.module';
@Component({
selector: 'body',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private router: Router,
private routerHelper: RouterHelper
) { }
ngOnInit() {
this.router.events
.filter(event => event instanceof NavigationEnd)
.map(() => this.route)
.map(route => {
let params: Array> = [];
params.push(route.params);
while (route.firstChild) {
route = route.firstChild;
params.push(route.params);
}
return Observable.combineLatest(params);
})
.mergeMap(params => params)
.map(params => params.reduce((prev, curr) => {
Object.keys(curr).forEach(key => {
prev[key] = curr[key];
});
return prev;
}, {}))
.subscribe(p => this.routerHelper.setParams(p));
}
}
It should be noted this approach doesn't handle nested child or auxiliary routes but if this is your use-case you cuold create a more complex pipeline to grab all those params or directly query the sibling, child or parent route parameters.
To use this data, inject the RouteHelper
service into your
desired component, subscribe to the params
observable and then
access the parameter you want using its key.
this.routeHelper.params
.subscribe(params => {
console.log(`user_id ${params['user_id']}, account_id ${params['account_id']}`);
});