How to lazy-load routes and import standalone components in Angular
Tags: angular, tutorial, beginners
Published: 2023-05-14 11:00:09

## 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

```typescript
// 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)
}];
```

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

```typescript
// 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())
  ]
});
```

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 &lt;router-outlet&gt; is the placeholder of the standalone component.  Finally, the page renders three routes for clicking.


![Home Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szj8vf7v13ysy6es2qwo.png)

###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

```typescript
{
  path: '',
  pathMatch: 'full',
  loadComponent: () => import('./home/home.component').then(mod => mod.HomeComponent)
}
```
```typescript
// 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 {}
```

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".

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

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

```typescript
// 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)));
}
```

###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.  

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

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

```typescript
// 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();
  }
}
```

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

The following Stackblitz repo shows the final results:

{%embed https://stackblitz.com/edit/angular-hl6z5z?file=src/main.ts %}

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:

- Github Repo: https://github.com/railsstudent/ng-pokemon/tree/main/projects/pokemon-demo-13
- Stackblitz: https://stackblitz.com/edit/angular-hl6z5z?file=src/main.ts
- Youtube: https://www.youtube.com/watch?v=3nh4ct7NExs
- PokeAPI: https://pokeapi.co/