How to lazy-load routes and import standalone components in Angular

Connie Leung - May 14 '23 - - Dev Community

Introduction

In Angular, routing is a key feature of application to navigate users to different pages to render components. A typical enterprise Angular application would lazy load routes and standalone components to maintain small main bundle to reduce the initial load time. In advanced case, such application even loads root-level and children routes to render parent and children components.

In this blog post, I would like to cover three cases of lazy loading single route and standalone component:

  • Lazy load default route
  • Lazy load routes that exist in the route array
  • Lazy load non-existing route

Define application route array



// app.route.ts

import { Route } from '@angular/router';

// lazy-load standalone component
export const APP_ROUTES: Route[] = [{
  path: 'pokemon',
  loadComponent: () => import('./pokemon/pokemon/pokemon.component')
    .then(mod => mod.PokemonComponent)
}, 
{
  path: '',
  pathMatch: 'full',
  loadComponent: () => import('./home/home.component').then(mod => mod.HomeComponent)
},
{
  path: '**',
  loadComponent: () => import('./page-not-found/page-not-found.component')
    .then(mod => mod.PageNotFoundComponent)
}];


Enter fullscreen mode Exit fullscreen mode

In app.route.ts file, I defined a route array and the application should lazy load the routes when they are clicked. When the path of the route is /pokemon, Angular imports PokemonComponent. When users reach the default URL, they should see HomeComponent standalone component. For other non-existent routes, Angular imports PageNotFoundComponent that redirects to home after 10 seconds.

Set up routes in the application



// main.ts

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [RouterLink, RouterOutlet],  // import RouteLink and RouterOutlet to use in inline template
  template: `
    <ul>
      <li><a routerLink="/" routerLinkActive="active">Home</a></li>
      <li><a routerLink="/pokemon" routerLinkActive="active">Show Pokemon</a></li>
      <li><a routerLink="/bad" routerLinkActive="active">Bad route</a></li>
    </ul>
    <router-outlet></router-outlet>
  `,
  styles: [`...omitted due to brevity...`]
})
export class App {}

bootstrapApplication(App, {
  providers: [
    provideHttpClient(),
    // provider to inject routes, preload all modules and trace route change events
    provideRouter(APP_ROUTES, withPreloading(PreloadAllModules), withDebugTracing())
  ]
});


Enter fullscreen mode Exit fullscreen mode

In main.ts, I provided APP_ROUTES to provideRouter provider. Moreover, the provider has two features; withPreloading that preloads all modules and withDebugTracing that traces routes in debug mode.

In the inline template, each hyperlink has routeLink directive that specifies the path and <router-outlet> is the placeholder of the standalone component. Finally, the page renders three routes for clicking.

Home Page

Lazy load default route

When browser first loads the application or user clicks 'Home', routeLink equals to '/' and it meets path: '' condition. Therefore, HomeComponent is imported



{
  path: '',
  pathMatch: 'full',
  loadComponent: () => import('./home/home.component').then(mod => mod.HomeComponent)
}


Enter fullscreen mode Exit fullscreen mode


// home.component.ts

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

@Component({
  selector: 'app-home',
  standalone: true,
    template: `
    <div>
      <h2>Click Pokemon Link</h2>
    </div>
  `,
  styles: [`
    :host {
      display: block;
    }
  `]
})
export class HomeComponent {}


Enter fullscreen mode Exit fullscreen mode

The simple component displays "Click Pokemon Link" header and serves its only purpose.

Lazy load routes that exist

When user clicks "Show Pokemon", routeLink changes to "/pokemon" and it matches the route path of "pokemon".



{
  path: 'pokemon',
  loadComponent: () => import('./pokemon/pokemon/pokemon.component')
    .then(mod => mod.PokemonComponent)
}


Enter fullscreen mode Exit fullscreen mode

The route eventually imports Pokemon component that also renders the rest of the child components.



// pokemon.component.ts

@Component({
  selector: 'app-pokemon',
  standalone: true,
  imports: [AsyncPipe, NgIf, PokemonControlsComponent, PokemonPersonalComponent, PokemonTabComponent],
  template: `
    ...render HTML and child components...  
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonComponent {
  retrievePokemon = retrievePokemonFn();
  pokemon$ = getPokemonId().pipe(switchMap((id) => this.retrievePokemon(id)));
}


Enter fullscreen mode Exit fullscreen mode

We hit a bad route, how do we fix it?

In the last scenario, user clicks "Bad route" but "/bad" does not exist in the routes array. Will the application break? Fortunately, I can define path: "**" route that catches all unmatched paths.



{
  path: '**',
  loadComponent: () => import('./page-not-found/page-not-found.component')
    .then(mod => mod.PageNotFoundComponent)
}


Enter fullscreen mode Exit fullscreen mode

This route renders PageNotFoundComponent and informs users that the page does not exist.



// page-not-found.component.ts

@Component({
  selector: 'app-page-not-found',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    <div>
      <h2>Page not found</h2>
      <p>Return home after {{ countDown$ | async }} seconds</p>
    </div>
  `,
  styles: [`...omitted due to brevity...`]
})
export class PageNotFoundComponent implements OnInit {
  countDown$ = timer(0, 1000)
    .pipe(
      map((value) => 10 - value),
      takeWhile((value) => value >= 0),
      shareReplay(1),
    );

  redirectHome$ = this.countDown$.pipe(
      tap((value) => {
        if (value <= 0) {
          inject(Router).navigate(['']);
        }
      }),
      takeUntilDestroyed()
    );

  ngOnInit(): void {
    this.redirectHome$.subscribe();
  }
}


Enter fullscreen mode Exit fullscreen mode

This component displays "Page not found" message and redirects home after 10 seconds.

The following Stackblitz repo shows the final results:

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.

Resources:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player