Search code examples
angularrouterbreadcrumbs

Angular 2 Dynamic Breadcrumbs


I need some help with breadcrumbs

Here example routes config

Routes = [  
    {path: '', redirectTo: 'home', pathMatch: 'full'},  
    {path: 'home', ..., data: { breadcrumb: 'Home'}},  
    {path: 'about', ..., data: { breadcrumb: 'About'}},  
    {path: 'github', ..., data: { breadcrumb: 'GitHub'},
    {
       path: 'category/:id',
       component: CategoryComponent
    },  
]

In bread crumbs component i try to use this to extract breadcrumbs data from ActivatedRoute

ngOnInit() {
   // subscribe to the NavigationEnd event
   this.router.events.filter((event) => event instanceof NavigationEnd).subscribe(() => {

      // set breadcrumbs
      const root: ActivatedRoute = this.activatedRoute.root;
      this.breadcrumbs = this.getBreadcrumbs(root);
   });
 }

After that i render comonentns template

<ul class="breadcrumb">
   <li><a routerLink="" routerLinkActive="active"><i class="fa fa-home position-left"></i> Главная</a></li>
   <ng-container *ngFor="let breadcrumb of breadcrumbs">
   <li routerLinkActive="active">
      <a [routerLink]="[breadcrumb.url, breadcrumb.params]"> {{ breadcrumb.label }}</a>
   </li>
   </ng-container>
</ul>

All fine but i dont know how i can pass data value from another component for category/:id route for make in human readable like "Mobile Phones"


Solution

  • I hope it's not too late to answer this question :D, so here is a working CodeSandbox example with two breadcrumb implementations, that uses the router data option to pass and collect data for the breadcrumb.

    Code explanation:

    Look at the comments inside the breadcrumb component.

    Here is the breacrumb component

    import { Component, OnInit } from "@angular/core";
    import {
      Router,
      Event,
      ActivationStart,
      ActivatedRouteSnapshot,
      ActivationEnd,
      NavigationEnd,
      NavigationStart
    } from "@angular/router";
    import { filter, map, buffer, pluck } from "rxjs/operators";
    
    /**
     * Check if an angular router 'Event' is instance of 'NavigationEnd' event
     */
    const isNavigationEnd = (ev: Event) => ev instanceof NavigationEnd;
    /**
     * Check if an angular router 'Event' is instance of 'NavigationEnd' event
     */
    const isActivationEnd = (ev: Event) => ev instanceof ActivationEnd;
    
    @Component({
      selector: "app-breadcrumb",
      templateUrl: "./breadcrumb.component.html",
      styleUrls: ["./breadcrumb.component.scss"]
    })
    export class BreadcrumbComponent implements OnInit {
      bcLoadedData;
      bcForDisplay;
    
      constructor(private router: Router) {}
    
      ngOnInit() {
        /**
         * navigationEnd$ is trigered once per completed routing event, in other words
         * once per loading a component that is in the end of the current route
         */
        const navigationEnd$ = this.router.events.pipe(filter(isNavigationEnd));
    
        /**
         * Here we subscribe to all navigation events, in order to update
         * our route "data", which we will use for the breadcrumb visualization.
         *
         * Than we filter the events emitted from the `router` in such way, that we are only
         * taking action upon a completed router event (in other words we are subscribing only for `ActivationEnd`
         * events)
         *
         * We use pluck to get only the requried bredcrumb data
         *
         * The buffer operator accumulates all `data` coming from the router and emmits the accumulated data once
         * when a `NavigationEnd` event passes troufh `navigationEnd$`
         *
         * The `map` in the end is used to reverse the collected data, in order to convert it to more logical
         * sequence. Without the revers first we will get the data for the current route, after that for it's parent
         * and so on (that is how the ng router works).
         */
        this.router.events
          .pipe(
            filter(isActivationEnd),
            pluck("snapshot"),
            pluck("data"),
            buffer(navigationEnd$),
            map((bcData: any[]) => bcData.reverse())
          )
          .subscribe(x => {
            this.bcLoadedData = x;
          });
      }
    }

    .breadcrumb {
        display: flex;
    
        &.simple {
            border: 2px solid blueviolet;
            border-radius: 4px;
        }
    
        .breadcrumb-separator {
            margin: 10px;
        }
    }
    
    <div class="breadcrumb simple">
        <div *ngFor="let bcElement of bcLoadedData; let last = last">
            {{bcElement.bc}}
            <span class="breadcrumb-separator"
                  *ngIf="!last">></span>
        </div>
    </div>
    

    Here is the structure of the routes

    const routes: Routes = [
    	{ path: '', pathMatch: 'full', component: MockComponentComponent, data: { bc: 'Looking outside' } },
    	{
    		path: 'home', component: MockComponentComponent,
    		data: { bc: 'I see a home' },
    		children: [
    			{
    				path: 'primaryHouse', component: MockComponentComponent,
    				data: { bc: 'I\'m going inside the home' },
    				children: [
    					{
    						path: 'kitchen', component: MockComponentComponent,
    						data: { bc: 'look inside the kitchen' }
    					},
    					{
    						path: 'bedroom', component: MockComponentComponent,
    						data: { bc: 'look inside the bedroom' }
    					}
    				]
    			},
    			{
    				path: 'guestHouse', component: MockComponentComponent,
    				data: { bc: 'I\'m going in the back yard' }
    			}
    		]
    	}
    ];