6

I am working with using the Angular router to dynamically add breadcrumbs. I have followed several examples and have gotten them to successfully work.

However, if I attempt incorporate a lazy-loaded module, I get the error:

Root segment cannot have matrix parameters

I have researched that issue and have not been able to find satisfactory information/fixes.

I've created a plunk with what I am trying to accomplish to save from extensive amounts of code on this page: https://plnkr.co/edit/505x2r

How can I continuing utilizing the dynamic breadcrumb creation from the router, but use lazy-loaded routes as well.

import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';

import { RootComponent } from 'src/root/root.component';
import { IndexComponent } from 'src/index/index.component';
import { SignupComponent } from 'src/signup/signup.component';
import { SigninComponent } from 'src/signin/signin.component';

@NgModule({
    imports: [
        RouterModule.forChild([
            {
            path: '',
            component: RootComponent,
            children: [
                {
                    path: 'signin',
                    loadChildren: 'src/signin/signin.module#SigninModule'
                },
                {
                    path: 'signup',
                    component: SignupComponent,
                    data: {
                        breadcrumb: 'Sign Up'
                    }
                },
                {
                    path: '',
                    component: IndexComponent
                }
            ]
        }
    ])
],
exports: [
    RouterModule
]
})
export class RootRoutingModule {}
Will Barnwell
  • 4,049
  • 21
  • 34
CodersCreed
  • 111
  • 1
  • 1
  • 5
  • https://github.com/gmostert/ng2-breadcrumb try this package – Robert Sep 07 '17 at 17:26
  • in `signin-routing.module.ts` : changing the empty path to a redirect makes it work without breaking : `{ path: '', redirectTo: 'signin', pathMatch: 'full' },`, but the route name is doubled : `/#/signin/signin` – Alex Beugnet Sep 07 '17 at 17:38
  • Check the changes I made. It is still not correct, but I could track the issue and from there you can go on: https://plnkr.co/edit/KTE39A?p=preview Do not copy and paste this code from the plunker as I changed a few things, just take a loot at the `html` of the breadcrumb. I removed the router link. You'' have to find a way to fix you router link issue with async mode. – DAG Sep 07 '17 at 18:12
  • I made this one a bit better: https://plnkr.co/edit/aOCxS2?p=preview – DAG Sep 07 '17 at 18:14

2 Answers2

5

Thank you to Robert, Alex Beugnet, and DAG for their comments and getting me on the right track to a solid solution!

The final plunk is here: https://plnkr.co/edit/iedQjH?p=preview

Here are the condensed issues I ran into:

First, I had lazy loaded routes where I had data on the route in the form of breadcrumb. When running through getBreadcrumbs, the breadcrumb label would show twice. I resolved this by adding the following lines:

  if (
    child.snapshot.url.map(segment => segment.path).length === 0
  ) {
      return this.getBreadcrumbs(child, url, breadcrumbs);
  }

Second, I had routes that were simply a parameter input, but I needed to have it listed on the breadcrumb trail. This was resolved by mapping the child.snapshot.url and assigning the segment to the label.

const routeArray = child.snapshot.url.map(segment => segment.path);
for ( let i = 0; i < routeArray.length; i++ ) {
    label = routeArray[i];
}

Third, I needed to show the parameter as well as the label for next route whether it was loaded immediately or lazy loaded via a module. I also ran into the following paths which needed to be split and labels assigned appropriately:

{
    path: ':id/products',
    loadChildren: 'src/products/products.module#ProductsModule',
    data: {
      breadcrumb: 'Products'
    }
}

and

                {
                  path: 'orders/:id',
                  component: OrderComponent,
                  data: {
                    breadcrumb: 'Orders'
                  }
                },

The solution to both was:

  for ( let i = 0; i < routeArray.length; i++ ) {
    if ( Object.keys(child.snapshot.params).length > 0 ) {
      if ( this.isParam(child.snapshot.params, routeArray[i]) ) {
        label = routeArray[i];
      } else {
        label = child.snapshot.data[ROUTE_DATA_BREADCRUMB];
      }
    } else {
      label = child.snapshot.data[ROUTE_DATA_BREADCRUMB];
    }
    const routeURL = routeArray[i];
    url += `/${routeURL}`;
    const breadcrumb: BreadCrumb = {
      label: label,
      params: child.snapshot.params,
      url: url
    };
    breadcrumbs.push(breadcrumb);
  }

private isParam(params: Params, segment: string) {
  for ( const key of Object.keys(params)) {
    const value = params[key];
    if ( value === segment ) {
      return true;
    }
  }
  return false;
}

With all good things, there one catch: YOU HAVE TO HAVE A ROUTE FOR EACH PARAMETER.

For example, if you have url: #/quotes/123456/products/123456/Generic/Tissue, you will need the following routes:

{
  path: ':id',
  component: ProductComponent,
  data: 'data'
},
{
  path: ':id/:make',
  component: ProductComponent,
  data: 'data'
},
{
  path: ':id/:make/:model',
  component: ProductComponent,
  data: 'data'
}

And you will end up with a clickable breadcrumbs that looks like this:

Home / Quotes / 123456 / Product / 123456 / Generic / Tissue

The project I am working on will only requires me to have one parameter... ever.

In the end, I am able to handle both routes loaded immediately and lazy-loaded routes which was my end-goal. I am sure there are some instances that I have not accounted for, but I feel it's pretty solid.

Regardless, I encourage folks to take what's here and run with it. Shoot me a note with any updates so I can what else this can become! Thx!

CodersCreed
  • 111
  • 1
  • 1
  • 5
  • I trying to apply this answer but what if i want my routes to be Home/Quotes/Quote.. i don't want to show the id but replaced with another descriptive text - what shall i do? Also if i don't want to show the last breadcrumb how / where can i alter the code for that? – bluePearl Dec 14 '17 at 09:05
  • this example is working fine for me but i have lazy loading module and i want to display breadcrumbs after login that is from home page.when i login breadcrumbs is displaying fine but after i refresh page breadcrumbs is not appear then i have load component by routing page , what should i do to display breadcrumbs on refresh?? – Bharti Ladumor Nov 03 '18 at 06:48
  • @CodersCreed , if you have stackblitz demo, kindly share it. thx – Mr. Learner Jan 04 '22 at 14:10
0

I spotted several elements that are involved with the issue, and got it to 'seemingly' work :

  • Your method getBreadcrumbs() gets called 3 times on a single navigation, which is a problem. Just add a console.log('I pass !'); at the start of the function to see it.

  • The main issue I believe is that you used a path for the lazy-loaded module. And for your breadcrumb component, it will use the empty path from your SignUpModule. This is what breaks because your breadcrumb component will use the path from your current activated route which is empty (''), and will effectively break the routing because you concat / with nothing. Changing this part in your component will make it work for the first time, as the routing to the lazy-loaded module path will be signup, and it will be able to register a routerLink that is not empty.

breadcrumb.component.ts

   let routeURL = child.snapshot.url.map(segment => segment.path).join('/');
   if(routeURL.length == 0) {
     routeURL = 'toto';
   }
   url += `/${routeURL}`;  
  • You should therefore find a way to use an empty route for your lazy-loaded module, so that the /signup path is for your signup component in his module. The route will be able to register correctly.
Alex Beugnet
  • 4,003
  • 4
  • 23
  • 40