Vincent Barrault

Faire un breadcrumb dans un projet Angular

Faire un breadcrumb dans un projet Angular

Table des matières

Faire un service pour récupérer la configuration

Notre breadcrumb va se servir de la configuration des routes pour trouver le contenu qu'il doit afficher, pour cela, nous allons créer un service qui va écouter les modifications de la route et reconstruire une liste avec une fonction recursive.

./app/breadcrumb.service.ts
import { Injectable } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { filter, distinctUntilChanged, map } from 'rxjs/operators';

export interface BreadcrumbItem {
    label: string;
    path?: string;
}

@Injectable({ providedIn: 'root' })
export class BreadcrumbService {
    public items$: Observable<BreadcrumbItem[]>;

    constructor(
        private _router: Router,
        private _route: ActivatedRoute
    ) {
        this.items$ = this._router.events.pipe(
            filter(event => event instanceof NavigationEnd),
            distinctUntilChanged(),
            map(_ => this._buildBreadCrumb(this._route.root))
        );
    }

    private _buildBreadCrumb(route: ActivatedRoute, url: string = '', breadcrumbs: BreadcrumbItem[] = []): BreadcrumbItem[] {
        const newBreadcrumbs = [...breadcrumbs];
        const path = route.snapshot.url.map(segment => segment.path).join('/');
        const nextUrl = `${url}/${path}`.replace('//', '/');

        if (route.routeConfig && route.routeConfig.data && route.routeConfig.data.breadcrumb) {
            let data = '';

            if (route.routeConfig.data.breadcrumb[0] === '@') {
                route.routeConfig.data.breadcrumb.split('.').forEach((level: string, index: number) => {
                    if (index === 0) {
                        data = route.snapshot.data[level.substr(1)];
                    } else {
                        data = !!data ? (data as any)[level] : null;
                    }
                });
            } else {
                data = route.routeConfig.data.breadcrumb;
            }

            newBreadcrumbs.push({
                label: data,
                path: nextUrl
            });
        }

        if (route.firstChild) {
            return this._buildBreadCrumb(route.firstChild, nextUrl, newBreadcrumbs);
        }

        return newBreadcrumbs;
    }
}

La liste d'éléments à afficher est stockée sous forme d'Observable et pourra être lue de façon dynamique.

Faire un composant pour afficher le breadcrumb

Pour lire cette liste d'éléments, nous allons créer un composant qui bouclera sur notre Observable et qui affichera son contenu.

./app/breadcrumb.component.ts
import { Component } from '@angular/core';
import { BreadcrumbService } from './breadcrumb.service';

@Component({
    selector: 'nav[app-breadcrumb]',
    template: `
        <ng-container *ngFor="let item of breadcrumbService.items$ | async; let last = last">
            <a *ngIf="!last" [routerLink]="item.path">{{ item.label }}</a>
            <span *ngIf="last">{{ item.label }}</span>
            <span *ngIf="!last">/</span>
        </ng-container>`,
})
export class BreadcrumbComponent {
    constructor(
        public breadcrumbService: BreadcrumbService
    ) {}
}

Faire un resolver pour afficher des données dans le breadcrumb

Enfin, nous devons aussi créer un resolver pour nos objets qui devront être chargés avant l'arrivée du composant.

C'est une étape obligatoire pour tous les objets qui seront chargés en fonction de l'url, mais elle n'est pas necessaire pour les textes écrits en dur dans le breadcrumb.

./app/user.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { of } from 'rxjs';
import { User, UserService } from './user.service';


@Injectable({ providedIn: 'root' })
export class UserResolver implements Resolve<Promise<User | null>> {
    constructor(
        private userService: UserService,
    ) {}

    resolve(route: ActivatedRouteSnapshot) {
        const id = route.paramMap.get('id');
        return of(this.userService.getUser(!!id ? +id : 0)).toPromise();
    }
}

Utilisation

Pour afficher le breadcrumb, nous pouvons simplement appeler son sélecteur comme ceci:

<nav app-breadcrumb></nav>

Et pour ce qui est du contenu du breadcrumb, nous allons le définir dans la configuration des routes:

const routes: Routes = [
    {
        path: '',
        data: { breadcrumb: `User` },
        children: [
            { path: '', component: UserListComponent },
            { path: 'create', component: UserCreateComponent, data: { breadcrumb: 'Create' } },
            { path: ':id', component: UserShowComponent, resolve: { user: UserResolver }, data: { breadcrumb: '@user.name' } },
        ]
    },
];