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)
}];
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())
]
});
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.
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)
}
// 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".
{
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.
// 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.
{
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.
// 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:
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.