7

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"

Grey2k
  • 464
  • 1
  • 7
  • 12

2 Answers2

8

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' }
   }
  ]
 }
];
  • 1
    What about page refresh? This triggers only when navigation happens, not if we are already on a route and page is refreshed. – Akshay Dec 01 '20 at 12:50
  • The aggregation of `data` works with page refreshes, so im not sure to what are you reffering. If you are reffering the part of the example that uses dynamicly aggregated data inside the breadcrumbs (e.g. the breakIns from the example) it's normal for them to be removed, as they are stored in memory and disappear on refresh. You can preserve does additional elements by adding them to you query parameters for example (and after that you can parse the query parameters and get your past breadcrumbs). – Християн Христов Dec 01 '20 at 17:34
  • 1
    My bad. Instead of app/main module I did this in one of the child module's sub component. So it didn't listen to initial page load. It only listen to router after that component's init. I guess this is a lesson that do this in main module. – Akshay Dec 02 '20 at 05:19
  • 1
    @ХристиянХристов do you have stackblitz for this buddy? – Mr. Learner Sep 29 '21 at 09:39
  • @Mr.Learner https://codesandbox.io/s/ppnw8o95pq i guess this should help you – Християн Христов Sep 29 '21 at 11:18
  • 1
    @ХристиянХристов thanks for your super quick response buddy. let me understand your implementation. Thanks alot for your effort – Mr. Learner Sep 29 '21 at 13:17
1

Any breadcrumb solution has to handle

  1. declarative approach of defining breadcrumbs in routing
  2. Dynamic asynchronous way to update any route by a different label
  3. way to skip specific routes from displaying in breadcrumbs

I have created a library to handle all these called xng-breadcrumbs - https://github.com/udayvunnam/xng-breadcrumb

feel free to check, An Angular app developed showing the breadcrumbs usage - https://xng-breadcrumb.netlify.com

Uday Vunnam
  • 337
  • 2
  • 5