@ngrx/router
is, at the moment, one of the best choices for a router component in Angular 2, and Vladivostok, the third iteration of Angular 2’s official router, will take a very heavy inspiration from it. At the moment we are using it to handle our routes, and the need arose to create a breadcrumb component for certain parts of the application.
You can see a working example here.
@ngrx/router
‘s API is very light and sparse, but not necessarily incomplete – a lot of the actual implementation is assumed to be left in the hands of the user. This is powered by the use of Observables for Route, RouteParams, QueryParams, etc. A great example of this assumption is the fact that instead of a set of interfaces like CanReuse/CanActivate/CanDeactivate, the router only ever activates components when they change after a route change – any changes in parameters are handled manually by default. More work, but also a clearer image of what one can and cannot do with the tool, and a lot more control.
The first thing we found was that routes have a property – options – that serves the express purpose of holding custom data. A simple usage is this:
export const routes: Route[] = [{ path: '/', component: Home, options: { breadcrumb: 'Home Sweet Home' }, children: [{ path: '/component-a', component: ComponentA, options: { breadcrumb: 'A Component' }, children: [{ path: '/one', component: One, options: { breadcrumb: 'The One' } }, [...] ] }, [...] ] }, ];
And the breadcrumb component is as such:
@Component({ selector: 'breadcrumbs', directives: [NgFor], template: `<span> <span *ngFor="let breadcrumb of breadcrumbs; let isLast = last"> <a [linkTo]="breadcrumb.path">{{breadcrumb.name}}</a> <span *ngIf="!isLast"> > </span> </span> </span>` }) export class Breadcrumbs { private breadcrumbs: any[]; constructor(private routerInstruction: RouterInstruction) { this.routerInstruction .subscribe( match => { this.breadcrumbs = this.getBreadcrumbs(match); } ); } private getBreadcrumbs(match: Match) { let breadcrumbs = []; let path = ''; for (let i = 0; i < match.routes.length; i++) { path = path[path.length - 1] === '/' ? path.substr(0, path.length - 1) : path; path += match.routes[i].path ? this.makePath(match.routes[i], match) : ''; if ((match.routes[i].options || {}).breadcrumb) { breadcrumbs.push({ name: match.routes[i].options.breadcrumb, path: path }); } } return breadcrumbs; } private makePath(route: Route, match: Match) { return pathToRegexp.compile(route.path)(match.routeParams); } }
RouterInstruction is one Observable that gives us all the information we need. By watching it, a Match object containing the array of matched routes is returned. All that was left was to create the urls, as @ngrx/router
uses only url strings (as opposed to the array notation you’ll find in @angular/router
, for instance) – but as @ngrx/router
uses path-to-regexp
to parse urls, to it was only a matter of using it to compile from the parsed data, and get the urls.
All in all, a very simple solution. Omitted are the use of translations and using asynchronously loaded data (like a profile name) in the breadcrumb – the former is trivial and very unrelated, and the latter we are using stores for, and it’s perhaps a good topic for another post.
This is really helpful, thanks. It looks like only half of the ngRx Router stuff is documented. The source code is actually relatively short. But, as I am new to RxJS in general, it can be a bit hard to follow. I was trying to understand how the RouterInstruction fit into it and what the “match” was actually doing.