Angular loadChildren using data for tab/navigation creation

1.2k Views Asked by At

I am trying to build a multi level navigation SPA.

The idea is to use the data block from the router.module.ts to build tabs and dynamcily load the child routes using loadChildren(). Each item on the navbar should then loop over the local routing.module for the sidebar menu.

At the top level (working) app-routing.module.ts

const routes: Routes = [
  {
    path: 'ci', component: CustInfoComponent,
    loadChildren: () => import('@_s_forms/cust-info/cust-info.module').then( m => m.CustInfoModule),
    data: {
      isTab:   true,
      tabName: 'Project Information',
      tabHint: ''
    }
  }, {
    path: 'dd', component: DeploymentDetailsComponent,
    data: {
      isTab:   true,
      tabName: 'Deployment (vm) Details',
      tabHint: 'VMs to be deployed'
    }
  }, {
    path: 'ei', component: ExtraInfoComponent,
    data: {
      isTab:   true,
      tabName: 'Additional Information',
      tabHint: ''
    }
  }, {
    path: '', redirectTo: MT4SizingConstants.startTab, pathMatch: 'full', data: { isTab: false }
  },
  { path: '**', redirectTo: '', data: { isTab: false } }
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { enableTracing: false })],
  exports: [RouterModule]
})
export class AppRoutingModule {}

The corresponding nav-bar ts and html look like:

export class HeaderComponent implements OnInit {
  /*
   Nav bar menu generated from route module via router.config.filter.
   Looks for isTab == true.

   Child routes can be added if required
   */


  startTab     = '';
  projectTitle = MT4SizingConstants.projectTitle;
  knownRoutes: Routes;


  constructor(
    private dbAccessService: DbAccessService,
    private router: Router,
    private location: Location,
  ) {
    this.knownRoutes = router.config.filter(
      qq => { if ( qq.data.isTab ) { return true; } }
    );
    this.startTab    = this.knownRoutes[ 0 ].path;
  }

  ngOnInit(): void {
    this.location.go(this.startTab);
  }

  onTabChange(e): void {
    this.router.navigateByUrl(e);
  }
}

html:

 <div class="d-flex ">
    <ul ngbNav #nav="ngbNav" [(activeId)]="startTab"
        (activeIdChange)="onTabChange($event)"
        class="nav-pills  "
        orientation="horizontal" destroyOnHide="false">
      <li *ngFor="let tabItem of knownRoutes;"
          [ngbNavItem]="tabItem.path"
          class="active"
      >
        <a ngbNavLink class="nav-link nav-link-color">{{tabItem.data.tabName}}</a>
        <ng-template ngbNavContent>
          <div>
            <router-outlet></router-outlet>
          </div>
        </ng-template>
      </li>
    </ul>
  </div>
  <div style="alignment: right">
    <h4>{{projectTitle}}</h4>
  </div>
</div>
<div [ngbNavOutlet]="nav" class="ml-4"></div>

This creates the primary navbar:

enter image description here

here is a child's routing module:

const ciRoutes: Routes = [
  {
    path: 'cust_details', component: CustomertDetailsComponent,
    data: {
      isTab:   true,
      tabName: 'Customer Details',
      tabHint: ''
    }
  },{
    path: 'proj_details', component: ProjectDetailsComponent,
    data: {
      isTab:   true,
      tabName: 'Project Details',
      tabHint: ''
    }
  },
  {
    path: '', redirectTo: 'proj_details', pathMatch: 'full', data: { isTab: false }
  },
  { path: '**', redirectTo: '', data: { isTab: false } }
];

@NgModule({
  imports: [RouterModule.forChild(ciRoutes)],
  exports: [RouterModule]
})
export class CustInfoRoutingModule {}

I am unable to access the data in ciRoutes that would allow the side menu to be build much like the primary navbar.

If I could access the index of the current route or somehow get ciRoutes all would be good.

This setup works just fine if all you need is a single nav element.

Thanks for your time

2

There are 2 best solutions below

2
On

You can start with adding the CustInfoComponent route to CustInfoModule routing module since you cannot declare a "component" and "loadChildren" attribute at the same time.

And then add the routes already existing in lazy loaded module as CustInfoComponent child components so that you can access currently active routes' children via ActivatedRoute from CustInfoComponent.

CustInfoRoutingModule would look like this:

const ciRoutes: Routes = [
  {
    path: '',
    component: CustInfoComponent,
    children: [
      {
        path: 'cust_details', component: CustomertDetailsComponent,
        data: {
          isTab:   true,
          tabName: 'Customer Details',
          tabHint: ''
        }
      },
      {
        path: 'proj_details', component: ProjectDetailsComponent,
        data: {
          isTab:   true,
          tabName: 'Project Details',
          tabHint: ''
        }
      },
      {
        path: '', redirectTo: 'proj_details', pathMatch: 'full', data: { isTab: false }
      },
    ]
  },
  { path: '**', redirectTo: '', data: { isTab: false } }
];

@NgModule({
 imports: [RouterModule.forChild(ciRoutes)],
 exports: [RouterModule]
})
export class CustInfoRoutingModule {}
0
On

I solved the issue by simply exporting the route object from each of the children's router module. I am then able to import the route object into the parent and generate the necessary tabs and <router-outlet></router-outlet> as necessary.

In the example above simply make the route object exported. I'm sure there is a more elegant way to accomplish this, but it works for me and, IMHO, easier to maintain.

export const ciRoutes: Routes = [....]

I was also able to create a simple template for the buttons/nav items to be generated:

Here is the html:

<div class="d-flex ">
  <ul ngbNav #ddNav="ngbNav" [(activeId)]="startTab"
      (activeIdChange)="onTabChange($event)"
      class="nav-pills"
      orientation="horizontal" destroyOnHide="false">
    <li *ngFor="let iMenu of knownRoutes;"
        [ngbNavItem]="iMenu.path"
        class="active"
    >
      <a ngbNavLink [routerLink]="iMenu.path"
         class="nav-link nav-link-color">{{iMenu.data.tabName}}</a>
    </li>
  </ul>
</div>

The component object:

import { Component, Input, OnInit } from '@angular/core';
import { Router, Routes }           from '@angular/router';

@Component({
  selector: 'mt4-sizing-menu-template',
  templateUrl: './menu-template.component.html',
  styleUrls: ['./menu-template.component.scss']
})
export class MenuTemplateComponent implements OnInit {
  @Input('allRoutes') InputRoutes: Routes;

  knownRoutes: Routes;
  startTab = '';

  constructor(
    private router: Router,
  ) { }

  ngOnInit(): void {
    this.knownRoutes = this.InputRoutes.filter(
      qq => { if ( qq.data.isTab ) { return true; } }
    );
    this.startTab    = this.knownRoutes[ 0 ].path;
  }
  onTabChange(e): void {
    this.router.navigateByUrl(e);
  }
}

Using the template

<mt4-sizing-menu-template [allRoutes]="lRoutes"></mt4-sizing-menu-template>
<router-outlet class="al2-nav2-background "></router-outlet>

And the required component for the html above

import { Component} from '@angular/core';
import { Routes}    from '@angular/router';
import { ciRoutes}  from '@_s_forms/customer-info/customer-info-routing.module';

@Component({
  selector:    'mt4-sizing-customer-info',
  templateUrl: './customer-info.component.html',
  styleUrls:   ['./customer-info.component.scss']
})
export class ProjectInfoComponent {
    
  lRoutes: Routes = ciRoutes;

  constructor() {}
}