Improve the performance of your app by using route-level code splitting!
This post explains how to set up route-level code splitting in an Angular application, which can reduce JavaScript bundle size and dramatically improve Time to Interactive.
You can find the code samples from this article on GitHub. The eager routing example is available in the eager branch. The route-level code splitting example is in the lazy branch.
Why code splitting matters
The ever growing complexity of web applications has led to a significant increase in the amount of JavaScript shipped to users. Large JavaScript files can noticeably delay interactivity, so it can be a costly resource, especially on mobile.
The most efficient way to shrink JavaScript bundles without sacrificing features in your applications is to introduce aggressive code splitting.
Code splitting lets you divide the JavaScript of your application into multiple chunks associated with different routes or features. This approach only sends users the JavaScript they need during the initial application load, keeping load times low.
Code splitting techniques
Code splitting can be done at two levels: the component level and the route level.
- In component-level code splitting, you move components to their own JavaScript chunks and load them lazily when they are needed.
- In route-level code splitting, you encapsulate the functionality of each route into a separate chunk. When users navigate your application they fetch the chunks associated with the individual routes and get the associated functionality when they need it.
This post focuses on setting up route-level splitting in Angular.
Sample application
Before digging into how to use route level code splitting in Angular, let's look at a sample app:
Check out the implementation of the app's modules. Inside AppModule
two routes are defined: the default route associated with HomeComponent
and a nyan
route associated with NyanComponent
:
@NgModule({
...
imports: [
BrowserModule,
RouterModule.forRoot([
{
path: '',
component: HomeComponent,
pathMatch: 'full'
},
{
path: 'nyan',
component: NyanComponent
}
])
],
...
})
export class AppModule {}
Route-level code splitting
To set up code splitting, the nyan
eager route needs to be refactored.
Version 8.1.0 of the Angular CLI can do everything for you with this command:
ng g module nyan --module app --route nyan
This will generate:
- A new routing module called NyanModule
- A route in AppModule
called nyan
that will dynamically load the NyanModule
- A default route in the NyanModule
- A component called NyanComponent
that will be rendered when the user hits the default route
Let's go through these steps manually so we get a better understanding of implementing code splitting with Angular!
When the user navigates to the nyan
route, the router will render the NyanComponent
in the router outlet.
To use route-level code splitting in Angular, set the loadChildren
property of the route declaration and combine it with a dynamic import:
{
path: 'nyan',
loadChildren: () => import('./nyan/nyan.module').then(m => m.NyanModule)
}
There are a two key differences from the eager route:
- You set
loadChildren
instead ofcomponent
. When using route-level code splitting you need to point to dynamically loaded modules, instead of components. - In
loadChildren
, once the promise is resolved you return theNyanModule
instead of pointing to theNyanComponent
.
The snippet above specifies that when the user navigates to nyan
, Angular should dynamically load nyan.module
from the nyan
directory and render the component associated with the default route declared in the module.
You can associate the default route with a component using this declaration:
import { NgModule } from '@angular/core';
import { NyanComponent } from './nyan.component';
import { RouterModule } from '@angular/router';
@NgModule({
declarations: [NyanComponent],
imports: [
RouterModule.forChild([{
path: '',
pathMatch: 'full',
component: NyanComponent
}])
]
})
export class NyanModule {}
This code renders NyanComponent
when the user navigates to https://example.com/nyan
.
To check that the Angular router downloads the nyan.module
lazily in your local environment:
- Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
Click the Network tab.
Click NYAN in the sample app.
Note that the
nyan-nyan-module.js
file appears in the network tab.
Find this example on GitHub.
Show a spinner
Right now, when the user clicks the NYAN button, the application doesn't indicate that it's loading JavaScript in the background. To give the user feedback while loading the script you'll probably want to add a spinner.
To do that, start by adding markup for the indicator inside the router-outlet
element in app.component.html
:
<router-outlet>
<span class="loader" *ngIf="loading"></span>
</router-outlet>
Then add an AppComponent
class to handle routing events. This class will set the loading
flag to true
when it hears the RouteConfigLoadStart
event and set the flag to false
when it hears the RouteConfigLoadEnd
event.
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
loading: boolean;
constructor(router: Router) {
this.loading = false;
router.events.subscribe(
(event: RouterEvent): void => {
if (event instanceof NavigationStart) {
this.loading = true;
} else if (event instanceof NavigationEnd) {
this.loading = false;
}
}
);
}
}
In the example below we've introduced an artificial 500 ms latency so that you can see the spinner in action.
Conclusion
You can shrink the bundle size of your Angular applications by applying route-level code splitting:
- Use the Angular CLI lazy-loaded module generator to automatically scaffold a dynamically loaded route.
- Add a loading indicator when the user navigates to a lazy route to show there's an ongoing action.