Nested outlets in master layout outlet in angular 6
Asked Answered
C

2

11

I am building an angular 6 application where I have two separate screens:

  1. Login (login, register, forgot password, profile)
    • those pages are not part of the layout
  2. Layout (dashboard, products, invoices)
    • those pages should share the same layout

My application is based on modules, so I have

  • app.module.ts
  • account.module.ts
  • dashboard.module.ts
  • products.module.ts
  • invoices.module.ts

Each module has empty component containing just an outlet, except the layout(containing header, body and footer).

I want to achieve those routes:

  • site.com/account/login
  • site.com | will navigate to the dashboard
    • site.com/dashboard
  • site.com/products
  • site.com/invoices

Visual explanation


I have no problem with adding the account.module, but I don't know how to configure the routes when adding the layout.module

Note: maybe my whole approach is wrong, but to me it is the only logical thing to do, since I have modules containing components, and I want to be prepared for possible lazy loading configuration.

Please advice me If I'm on the wrong path.

Code

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';

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

import { LayoutModule } from './layout/layout.module';
import { AccountModule } from './feature-components/membership/account.module';


@NgModule({
  declarations: [
    AppComponent,
    FetchDataComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    BrowserAnimationsModule,
    HttpClientModule,
    LayoutModule,
    AccountModule,
    BrowserAnimationsModule
    RouterModule.forRoot([
     { path: '', component: LayoutOutletComponent },
     { path: '**', component: PageNotFoundComponent }
    ]),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.html

<router-outlet></router-outlet>

account.module.ts

import { NgModule } from '@angular/core';
import { SharedModule } from '@app-shared/shared.module';
import { LoginComponent } from './login/login.component';
import { ResetPasswordComponent } from './reset-password/reset-password.component';
import { ChangePasswordComponent } from './change-password/change-password.component';
import { RouterModule } from '@angular/router';
import { AccountOutletComponent } from './account-outlet/account-outlet.component';

@NgModule({
  imports: [
    SharedModule,
    RouterModule.forChild(
      [{
        path: 'account', component: AccountOutletComponent,
        children: [
          { path: 'login', component: LoginComponent },
          { path: 'reset-password', component: ResetPasswordComponent },
          { path: 'change-password', component: ChangePasswordComponent },
          { path: '', redirectTo: 'login', pathMatch: 'full' }
        ]
      }]
    )
  ],
  exports: [
    LoginComponent,
    ResetPasswordComponent,
    ChangePasswordComponent
  ],
  declarations: [
    AccountOutletComponent,
    LoginComponent,
    ResetPasswordComponent,
    ChangePasswordComponent
  ]
})
export class AccountModule { }

So, no problems here, the problems starts when I add that layout module which should constants the layout and inside of it, all nested modules.. and to be honest I don't even know how to start the routing configuration

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

import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { FooterComponent } from './footer/footer.component';
import { LayoutOutletComponent  } from './layout-outlet/layout-outlet.component';
import { InvoicesModule } from '../feature-components/invoices/invoices.module';


@NgModule({
  imports: [
    CommonModule,
    InvoicesModule,
    RouterModule
  )],
  exports: [
    NavMenuComponent,
    FooterComponent
  ],
  declarations: [
    NavMenuComponent,
    FooterComponent,
    LayoutOutletComponent
  ]
})
export class LayoutModule { }
Cornish answered 20/6, 2018 at 8:41 Comment(0)
I
5

I think you're on the right path. First you need to deal with eager routes and then you can switch to lazy modules.

What you need to do:

app.module.ts

@NgModule({
  imports: [
    BrowserModule,
    AccountModule,
    LayoutModule,
    RouterModule.forRoot([
      { path: '**', component: PageNotFoundComponent }
    ])
  ],
  declarations: [
    AppComponent,
    PageNotFoundComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

layout.module.ts

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
      {
        path: '',
        component: LayoutOutletComponent,
        children: [
          {
            path: 'dashboard',
            loadChildren: () => DashboardModule
          },
          {
            path: 'products',
            loadChildren: () => ProductsModule
          },
          { path: '', redirectTo: '/dashboard', pathMatch: 'full' }
        ]
      }
    ])
  ],
  declarations: [
    LayoutOutletComponent
  ]
})
export class LayoutModule { }

The main thing you need to know is that all angular routes are merged in one route configuration. To understand this I'd suggest you watching a great video by @deborahk

You can't just write

{ path: '', component: LayoutOutletComponent},

and import other modules(ProductsModule, DashboardModule) separately. The nested routes should be provided as child routes.

Now, when you have all routes configured you can easily switch to lazy loaded modules:

{
  path: '',
    component: LayoutOutletComponent,
    children: [
      {
        path: 'dashboard',
        loadChildren: 'pathToModule#DashboardModule'
      },
      {
        path: 'products',
        loadChildren: 'pathToModule#ProductsModule'
      },
      { path: '', redirectTo: '/dashboard', pathMatch: 'full' }
    ]
}

And also you can lazy load LayoutModule in AppModule

@NgModule({
  imports: [
   ...
    LayoutModule,
    RouterModule.forRoot([
      { path: '', loadChildren: './layout/layout.module#LayoutModule' },
      { path: '**', component: PageNotFoundComponent }
    ])
  ],
  ...
})
export class AppModule { }

I've created ng-nested-outlets app on github so try it out.

You can also try it online on Stackblitz Demo

See also

Ionone answered 20/6, 2018 at 10:51 Comment(4)
If you want to enhance my answer use the edit button. It's basically the same as mine.Perch
@yt61 Your configuration in layout module is wrong. dashboard, products and invoices should be child routes in order those components will be placed inside layout outletIonone
@yurzui, so now you just lazy load the whole LayoutModule, how can we lazy load just the ProductsModule, and the Dashboard to be eagerly loaded ?Cornish
@Ionone Great! Thanks! It works. Just one thing now.. when I publish the project using --prod, and when i have: ` loadChildren: () => DashboardModule` it fails on IIS, complaining about: ERROR Error: Uncaught (in promise): Error: Runtime compiler is not loaded Error: Runtime compiler is not loaded But if I change it to be: loadChildren: '(pathToDashModule)#DashboardModule' it works fine on iis, but if I do this, I can see that it is switched on lazy loading - which I initially don't want to useCornish
P
1

You shoud do the following things

  • in the app module create routes to load modules not the components
  • in layout module create routes to load Dashboard, Product and InvoicesModule
  • in layout component setup header router-outlet and footer and/or other layout elements
  • in modules Dashboard, Product and Invoices setup routes to load component

app.module

    RouterModule.forRoot([
      { path: '', loadChildren: '(PathToLayoutMoudle)#LayoutModule' },
      { path: '**', component: PageNotFoundComponent }
    ]),

layout.module

    RouterModule.forChild([
      { path: '', component: LayoutComponent},
          children: [
          { path: 'dashboard',loadChildren: 
              PathToDashboardMoudle)#DashboardMoudle'},
          { path: 'products',loadChildren: 
              PathToProductsMoudle)#ProductsMoudle'},
          { path: 'invoices',loadChildren: 
              PathToInvoicesMoudle)#InvoicesdMoudle'},
      ]
    ]),

account.module

  RouterModule.forChild(
  [{
    path: '', component: AccountOutletComponent,
    children: [
      { path: 'login', component: LoginComponent },
      { path: 'reset-password', component: ResetPasswordComponent },
      { path: 'change-password', component: ChangePasswordComponent },
      { path: '', redirectTo: 'login', pathMatch: 'full' }
    ]
  }]
Perch answered 20/6, 2018 at 9:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.