Sep 28th, 2017 - written by Kimserey with .
Last month I showed how we could build a breadcrumb with PrimeNG in Angular (you can read it as appetizer if you are interested in implementing a breadcrumb bar). In my previous post, I suggested to have a service BreadcrumbService
which would hold the crumbs and get updated in ngOnInit
of the component used on the route. Since then, I always was uncomfortable with this approach as this meant that my component would know the existance of a breadcrumb, because it updates it, while I always believed it should not know and not care.
This led me to figure another way to abstract away from the component the concept of breadcrumb by combining guard, resolver and route.
Registering data on the route is an easy way to inject constant data into the activated route. The activated route can then be injected anywhere in all elements under the route like a component, a resolver or a guard.
For example if our route needs the following breadcrumb:
1
Item1 > Item2 > Item3
We can add the data in the route as followed:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
path: 'breadcrumb2',
component: MyBreadcrumbed2Component,
data: {
crumbs: [{
label: 'test1'
}, {
label: 'test2'
}, {
label: 'test3'
}]
}
}
What we’ve done in the previous post was to create a service holding the crumbs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Injectable()
export class BreadcrumbService {
private crumbs: Subject<MenuItem[]>;
crumbs$: Observable<MenuItem[]>;
constructor() {
this.crumbs = new Subject<MenuItem[]>();
this.crumbs$ = this.crumbs.asObservable();
}
setCrumbs(items: MenuItem[]) {
this.crumbs.next(
(items || []).map(item =>
Object.assign({}, item, {
routerLinkActiveOptions: { exact: true }
})
)
);
}
}
Before our page is shown, we want the breadcrumb to be filled up. Therefore we need to set it. The ideal place to do that is in a guard by implementing CanActivate as we make sure that we set the crumbs before returning true:
1
2
3
4
5
6
7
8
9
10
11
@Injectable()
export class BreadcrumbInitializedGuard implements CanActivate {
constructor(private service: BreadcrumbService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const crumbs = route.data['crumbs'];
this.service.setCrumbs(crumbs);
return true;
}
}
And we add it to the route:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
path: 'breadcrumb2',
component: MyBreadcrumbed2Component,
canActivate: [ BreadcrumbInitializedGuard ],
data: {
crumbs: [{
label: 'test1'
}, {
label: 'test2'
}, {
label: 'test3'
}]
}
}
If you test what we done so far, the application should run but you will notice that the breadcrumb is not filled up. The observable returns nothing even though the crumbs are set in the guard.
The reason is that the subscription to the crumbs in the component happens after the crumbs are set which means it will never get passed to the subscription as it is the past.
What we need is to replay the stream on subscription. To do that we can use the ReplaySubject
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Injectable()
export class BreadcrumbService {
private crumbs: ReplaySubject<MenuItem[]>; // <-- Change to ReplaySubject
crumbs$: Observable<MenuItem[]>;
constructor() {
this.crumbs = new ReplaySubject<MenuItem[]>();
this.crumbs$ = this.crumbs.asObservable();
}
setCrumbs(items: MenuItem[]) {
this.crumbs.next(
(items || []).map(item =>
Object.assign({}, item, {
routerLinkActiveOptions: { exact: true }
})
)
);
}
}
And we are done, we now have a component without knowledge of the breadcrumb.
The full source code is available in my GitHub https://github.com/Kimserey/ng-samples/blob/master/src/app/primeng/prime-ng.module.ts.
Today we revisited our implementation of the breadcrumb bar. We made use of the tools provided by Angular router to abstract away the concept of navigation out of the component implementation. This technique of combining guard and service can be used in many scenarios where initialization is required. Hope you enjoyed this post as much as I enjoyed writting it. If yoy have any questions leave it here or hit me on Twitter @Kimserey_Lam. See you next time!