deamon
deamon

Reputation: 92539

Angular breadcrumbs with dynamic label

I want to create a breadcrumb navigation with one dynamic item. Something like this:

Home > Category A > Subcategory 1 > XYZ

Where "Category A" and "Subcategory 1" are static and "XYZ" is dynamic. This dynamic label exists only after the respective component is initialized, because the content gets loaded from a remote server. The hierarchy comes from the router configuration where routes have children.

I've tried to access the current component via ActivatedRoute, but all I could find was the component name, what doesn't help in this regard. My next idea was to watch the RouterOutlet for changes, but there seems to be no event being fired on change.

My vague idea is to let components implement an interface like this:

interface Named {
  readonly name: Observable<string>
}

... and somehow subscribe to this Observable, but in order to do so, I need the component instance!

How can I get a computed string from the component currently displayed in the RouterOutlet?

Upvotes: 2

Views: 9932

Answers (2)

andreivictor
andreivictor

Reputation: 8491

Instead of using the component’s ngOnInit hook to call the method from the service that will bring the necessary data, my proposal is to use a Route Resolver.

Angular Route Resolvers are used in order to pre-fetch some data while the user is redirecting from one Route to another. The newly available page (component) will already have the data that is required to be rendered in the page.

Basically, a Resolver is a service that implements the Resolve interface: https://angular.io/api/router/Resolve.

From the docs:

The interface defines a resolve() method that is invoked when the navigation starts. The router waits for the data to be resolved before the route is finally activated.

In the following examples, I have removed a level from your hierarchy example and I've assumed that the subcategory is dynamic.

The code for the resolver will be:

import { Injectable } from '@angular/core';
import { Resolve, RouterStateSnapshot,ActivatedRouteSnapshot} from "@angular/router";
import { HttpClient } from "@angular/common/http";
import { Observable, of } from "rxjs";

@Injectable({
  providedIn: 'root'
})

export class DynamicSubcategoryResolver implements Resolve<any> {
  
  constructor(private _http: HttpClient) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> {

    const subcategoryId: string | null = route.paramMap.get("subcategoryId");
    // other route params are in route.paramMap


    // return this._http.get("url");
    return of({
      breadcrumb: "(Dynamic breadcrumb) Subcategory " + subcategoryId,
      foo: "bar",
      bar: "baz"
    });
  }
} 

Now, the routes are defined like below:

const routes: Routes = [
  {
    path: "",
    component: HomeComponent,
    data: {
      breadcrumb: "Home"
    },
    children: [
      {
        path: "category-a",
        component: CategoryAComponent,
        data: {
          breadcrumb: "CategoryA" // static breadcrumb
        },
        children: [
          {
            path: "subcategory/:subcategoryId",
            component: DynamicSubcategoryComponent,
            resolve: {
              apiData: DynamicSubcategoryResolver
            }
          }
        ]
      }
    ]
  }
];

The data from the resolver will be available in: ActivatedRoute.snapshot.data object:

export class DynamicSubcategoryComponent implements OnInit {

  constructor(private activatedRoute: ActivatedRoute) {}

  ngOnInit() {
    const data = this.activatedRoute.snapshot.data;
    console.log("apiData: ", data.apiData);
  }
}

Finally, in the breadcrumbs generator, the code should look like:

const routeData: Data = route.snapshot.data;

if (routeData.breadcrumb) {
   breadcrumbs.push({
     label: routeData.breadcrumb, 
     url: url 
   });
} else if (routeData.apiData && routeData.apiData.breadcrumb) {
  breadcrumbs.push({
    label: routeData.apiData.breadcrumb,
    url: url
  });
}

The fully working code: https://stackblitz.com/edit/angular-ivy-vuroqk

Upvotes: 5

Maximillion Bartango
Maximillion Bartango

Reputation: 1599

Try using router and route:

import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';

and:

constructor(private router: Router, private route: ActivatedRoute) {}

then:

router.events.forEach(event => {
  if (event instanceof NavigationEnd) {
    // access all route data here
    this.route = route;
  }
});

Upvotes: 0

Related Questions