angular 9 nested lazy loaded modules with nested router outlets

Viewed 2314

I'm trying to develop SPA using angular 9, I almost try to lazy load every component and all of its children. My problem arose when I tried to have router-outlet inside one of the lazy-loaded components and I want this router-outlet to be used to load children components (which is lazy-loaded also). when I do so, I always get all the nested lazy-loaded components loaded in the main router-outlet in the app.component.html instead of the router-outlet in the nested lazy-loaded component

app.component template:

<app-navbar></app-navbar>
<router-outlet></router-outlet>

app-routing.module:

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'login', component: LoginComponent },
  { path: 'articles', loadChildren: () => import('./articles-viewer/articles-viewer.module').then(m => m.ArticlesViewerModule) },
  { path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule) },
  { path: '**', redirectTo: ''}
];

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

app.module:

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    LoginComponent,
    NavbarComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    BrowserAnimationsModule,
    AppAngularMaterial
  ],
  providers: [
    AppHttpInterceptors
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

navbar.component template:

<a routerLink="">Home</a>
<a routerLink="/articles">Articles</a>
<a routerLink="/dashboard">Dashboard</a>

dashboard template:

<a routerLink="statistics">Statistics</a>
<router-outlet></router-outlet>

dashboard.module :

@NgModule({
  declarations: [DashboardComponent],
  imports: [
    CommonModule,
    DashboardRoutingModule,
    AppAngularMaterial
  ]
})
export class DashboardModule { }

dashboard-routing.module:

const routes: Routes = [
  { path: '', component: DashboardComponent },
  { path: 'statistics', loadChildren: () => import('./statistics/statistics.module').then(m => m.StatisticsModule) }
  ];

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

In a nutshell, the statistics component (a lazy-loaded component) is loaded in the router-outlet declared in the app.component template rather than in the router-outlet in the dashboard template and the dashboard component is unloaded

I tried to find the solution in angular.io but all that I got is about managing multiple outlets in the same component. Also, this is what is I got when googling this problem.

is there any solution for this?

2 Answers

I had a similar issue! The problem on my side was that I imported the lazy-loaded Module and so the routing definition was visible for the app router-outlet.

If you need an example, feel free and write a comment.

UPDATE: 2021-08-24 added full code example

the following example can be extended indefinitely:

Folder structure:

enter image description here

1 app.module:

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";

import { CoreModule } from "@core/core.module";
import { AppRoutingModule } from "@routes/app-routing.module";

import { AppComponent } from "./app.component";

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, CoreModule, AppRoutingModule, BrowserAnimationsModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

2 app.component:

import { Component } from "@angular/core";

@Component({
  selector: "rot-root",
  template: ` <router-outlet></router-outlet> `, // <===== ROOT ROUTER-OUTLET
  styleUrls: ["./app.component.scss"],
})
export class AppComponent {}

3 app-routing.module:

import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";

export const ROUTES: Routes = [
  {
    path: "",
    pathMatch: "prefix",
    loadChildren: () =>
      import("./main/main-routing.module").then((m) => m.MainRoutingModule),
  },
];

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

4 main-routing.module:

import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";

import { layoutModules } from "./layouts";

import { MainLayoutViewComponent } from "./layouts/main-layout/views";

export const SUBROUTES: Routes = [
  {
    path: "",
    pathMatch: "prefix",
    component: MainLayoutViewComponent, // <==== DISPLAYED IN ROOT ROUTER-OUTLET
    children: [ // <==== CHILDREN ARE DISPLAYED IN SUB ROUTER-OUTLET (6)
      {
        path: "dashboard",
        loadChildren: () => import("./dashboard/dashboard.module").then(m => m.DashboardModule)
      },
      // <==== A LOT OF ROUTES FROM THE MAIN FOLDER - SAME AS DASHBOARD ====>
    ],
  },
];

@NgModule({
  declarations: [],
  imports: [
    RouterModule.forChild(SUBROUTES),
    ...layoutModules
  ],
  exports: [RouterModule],
})
export class MainRoutingModule {}

5 main-layout.module:

import { NgModule } from "@angular/core";
import { SharedModule } from "@shared/shared.module";

// material modules
// <==== a lot of material imports :D ====>

// module components
import { views } from "./views"; // <===== HERE I IMPORT THE MAIN-LAYOUT-VIEW
import { containers } from "./containers";
import { components } from "./components";

@NgModule({
  declarations: [...views, ...containers, ...components],
  imports: [
    SharedModule,
    // <==== a lot of material imports :D ====>
  ],
})
export class MainLayoutModule {}

6 main-layout-view.component:

import { Component, OnInit } from "@angular/core";

import { Observable } from "rxjs";

import { SidenavService } from "@core/services";

@Component({
  selector: "rot-main-layout-view",
  template: `
    <rot-main-navbar></rot-main-navbar>
    <mat-drawer-container autosize hasBackdrop>
      <mat-drawer mode="over" [opened]="opened$ | async">
        <rot-main-sidenav></rot-main-sidenav>
      </mat-drawer>
      <div class="content-wrapper">
        <router-outlet></router-outlet>          <==== SUB ROUTER-OUTLET
      </div>
    </mat-drawer-container>
  `,
  styleUrls: ["./main-layout-view.component.scss"],
})
export class MainLayoutViewComponent implements OnInit {

  constructor(private _sidenavService: SidenavService) {}

  ngOnInit(): void {}

  get opened$(): Observable<boolean> {
    return this._sidenavService.opened$;
  }
}

In dashboard-routing.module

Instead of this:

const routes: Routes = [
    { path: '', component: DashboardComponent },
    { path: 'statistics', loadChildren: () => 
        import('./statistics/statistics.module').then(m => m.StatisticsModule)
    }
];

Do this:

const routes: Routes = [
    { path: '', component: DashboardComponent,
      children: [
          { path: 'statistics', loadChildren: () => 
           import('./statistics/statistics.module').then(m => m.StatisticsModule)
          }
      ]
    }
];
Related