Search code examples
angulartypescriptangular-routingangular-routerangular-module

Dynamically loaded module causes Angular `<router-outlet>` to display twice


The solution to this is somewhere in Angular <router-outlet> displays template twice, but I'm unable to apply it because my example differs due to dynamically loaded modules.

I have a nested <router-outlet> setup;

  • localhost:4200/login renders the LoginComponent in the top-level component, AppComponent
  • localhost:4200/main renders the MainComponent in the top-level component, AppComponent
    • MainComponent contains a <router-outlet> in which LeafComponent is rendered.

The app-structure:

app.component.ts       <router-outlet>
components/
    login/
        login-component.ts
    main/
        main-module.ts
        main-component.ts       <router-outlet>
        components/
            leaf-component.ts

The following is taken from app.module and works iff main-module is not dynamically loaded. I.e. it only works if main-component is instantiated.

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['/login']);
const redirectLoggedInToMain = () => redirectLoggedInTo(['/main']);

const appRoutes: Routes = [
  {
    path: 'login',
    pathMatch: 'full',
    component: LoginComponent,
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: redirectLoggedInToMain },
  },
  {
    path: '',
    component: MainComponent,
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin },
    children: [

      { path: '', redirectTo: '/main', pathMatch: 'full' },
      // ----> Render leaf-component in main-component <router-outlet>
      {
        path: 'main',
        pathMatch: 'full',
        component: LeafComponent,
      },

    ],
  },
  { path: '**', component: PageNotFoundComponent },
];

This works. But when dynamically loading the LeafModule, the MainComponent is rendered twice. I.e. it does not work when main-module is dynamically loaded and then main-component is instantiated.

const appRoutes: Routes = [
  {
    path: 'login',
    pathMatch: 'full',
    component: LoginComponent,
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: redirectLoggedInToMain },
  },
  {
    path: '',
    component: MainComponent,
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin },
    children: [

      { path: '', redirectTo: '/main', pathMatch: 'full' },
      // ----> Dynamically load leaf-module
      // ----> Render leaf-component in main-component <router-outlet>
      {
        path: 'main',
        pathMatch: 'full',
        loadChildren: () => import('./components/main/components/leaf/leaf.module').then(m => m.LeafModule)
      },

    ],
  },
  { path: '**', component: PageNotFoundComponent },
];

Solution Details

@inge-olaisen's suggestion solved my problem but I wanted to add some detail here for completeness.

My mistake was not appreciating that a dynamically loaded module MUST(?) have a route, even if that route just loads the default modulel:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LeafComponent } from './leaf.component';

const routes: Routes = [
    {
        path: '',
        component: LeafComponent,
        pathMatch: 'full',
    },
];
@NgModule({
    imports: [
        RouterModule.forChild(routes),
    ],
    exports: [RouterModule]
})
export class LeafRoutingModule { }

This still seems a little redundant since because there are no routes. But because I want to lazy load the component, I must add them.

I also used separate routing modules which did make things clearer.

Finally, Angular 8 | Nested Routing with Multiple RouterOutlet using loadChildren having own Router Modules Example Application, is probably the simplest and most complete example of nested routing with lazy-loaded modules out there (IMO). It also uses a recent version of Angular and thus the new load loadChildren: () => import('./foo.module').then(m => m.FooModule) syntax. It also has a StackBlitz demo.


Solution

  • We're missing some key information. Does the leaf.module have it's own router module? A leaf-routing.module.ts for instance. That routing module should in turn have pathmatching for '' which loads the LeafComponent.

    If you don't have that routing module, the module doesn't know which route to look for and I'm assuming the problem is the same as the person you linked to had, but instead you're missing the '' path for the LeafRoutingModule.