Angular 9 has some pretty awesome new features. The runtime, code-name Ivy, opens the doors to things like making lazy load Angular components more straightforward than ever.
This article shows you how to lazy load with Angular 9 and provides the code and resources along the way.
1 - Create a New App
Create a new Angular app using the Angular CLI command below. The following code will generate an app with as few files as you can get.
ng new lazy-demo
--minimal
--inline-template
--inline-style
--routing=false
--style=css
This command will create a new angular app in a folder named lazy-demo
-
--minimal
removes removes testing frameworks -
--inline-template
puts all component templates in the.ts
file -
--inline-styles
puts all component styles in the.ts
file -
--routing=false
does not add any routing -
--style=css
specifies to use CSS
2 - Create Lazy Components
Create two new components named lazy1
and lazy2
.
ng g c lazy1 --flat --skip-import --skip-selector
ng g c lazy2 --flat --skip-import --skip-selector
These commands will create the two new components in files named lazy1.component.ts
and lazy2.component.ts
, respectively. We won't want either component to be declared in a module, since we want to lazy load them. If we declare them in a module, then Angular will eagerly load them.
We're also not creating the selectors since we won't be referring to them in a template directly. Instead, we'll load them dynamically.
3 - Lazy Load the Components
Add the following code to the file app.component.ts. Notice the constructor injects a ViewContainerRef
(a place to put our components) and a ComponentFactoryResolver
(this creates our components in code).
export class AppComponent {
title = 'lazy-comp';
constructor(
private viewContainerRef: ViewContainerRef,
private cfr: ComponentFactoryResolver
) {}
async getLazy1() {
this.viewContainerRef.clear();
const { Lazy1Component } = await import('./lazy1.component');
this.viewContainerRef.createComponent(
this.cfr.resolveComponentFactory(Lazy1Component)
);
}
async getLazy2() {
this.viewContainerRef.clear();
const { Lazy2Component } = await import('./lazy2.component');
this.viewContainerRef.createComponent(
this.cfr.resolveComponentFactory(Lazy2Component)
);
}
}
The getLazy1
function clears the container. This is important is we only want to show one of the lazy-loaded components at a time. If we did not clear the container, every time we lazy load components, they would be displayed one after another.
Next, we import the components, lazily, using the await import
syntax.
Finally, we create the component in the container.
4 - Adding Buttons to Lazy Load
Modify the template in app.component.ts
, as shown below. This adds buttons that will lazy load each component when clicked.
template: `
<div>
<div>Hello World! This is the {{ title }} app.</div>
<button (click)='getLazy1()'>lazy 1</button>
<button (click)='getLazy2()'>lazy 2</button>
</div>
`
5 - Watch it Lazy Load
Now run the app with ng serve
and browser to http://localhost:4200. After the app loads, open the browser's developer tools. Then clear the network traffic, so we can see when the components are lazy-loaded.
When you click one of the buttons, notice that the associated component I displayed and the network traffic shows the component is lazy loaded.
6 - What if Lazy Loaded Components Have Children
This is cool, but what if a lazy-loaded component has child components of its own? Imagine that Lazy2Component
needs to show two other components named Lazy2aComponent
and Lazy2bComponent
. We'll need to generate these two components, and once again, make sure we do not declare them in a module.
ng g c lazy2a --flat --skip-import --skip-selector
ng g c lazy2b --flat --skip-import --skip-selector
Now modify the Lazy2Component
to load it's two child components. We'll once again use the ViewContainerRef
and ComponentFactoryResolver
.
However, this time we will not lazy-load the children. Instead, we'll create the child components in the ngOnInit
and import them synchronously.
What's the difference? Well, in this example, these child components will load in the same bundle as their parent, Lazy2Component
.
Modify your Lazy2Component
code, as shown below.
import {
Component,
ViewContainerRef,
ComponentFactoryResolver,
OnInit
} from '@angular/core';
import { Lazy2aComponent } from './lazy2a.component';
import { Lazy2bComponent } from './lazy2b.component';
@Component({
template: `
<p>lazy2 component</p>
`
})
export class Lazy2Component implements OnInit {
constructor(
private viewContainerRef: ViewContainerRef,
private cfr: ComponentFactoryResolver
) {}
ngOnInit() {
const componentFactorya = this.cfr.resolveComponentFactory(Lazy2aComponent);
const componentFactoryb = this.cfr.resolveComponentFactory(Lazy2bComponent);
this.viewContainerRef.createComponent(componentFactorya);
this.viewContainerRef.createComponent(componentFactoryb);
}
}
7 - Run the App
Now run the app again and browse to http://localhost:4200. Or go back to the browser if you never stopped serving it.
Open the browser's developer tools, go to the Network tab, and clear the network traffic.
Notice that when you click on the button to load the Lazy 1 component that the bundle for that component is passed, and Lazy 1 is displayed.
When you click the button to load Lazy 2 its bundle is passed, and Lazy 2, Lazy 2a, and Lazy 2b are all displayed.
The bundle sizes for Lazy 1 and 2 are different, too. Lazy 1 only has a single component, so it is smaller than Lazy 2 (which contains three components).
Should You?
So now you know how to lazy load a component with Angular 9. You can lazy load a component and have its children in turn lazily load or eagerly load. But you could also do this with a module (specifically an NgModule
). So what do you do? Lazy loading a component helps support scenarios where you want to access features without routing. Lazy loading of modules helps when you want to access features with routing. But should that line be so distinct? Perhaps that line will blur as time moves forward. There are no warning signs here, just things to consider before entering this arena.
Another scenario might be when you want to load component dynamically based on user profile or a workflow. You could dynamically load (eagerly or lazily) one or more components.
Learn More
These examples should be able to help you get started with lazy loading components dynamically, with or without children. If you want to learn more, check out these resources:
- Dynamic Component Loader
- 7 new features in Angular 9.
- VS Code editor
- Angular Essentials Extension for VS Code
- Angular Language Service for VS Code
- Angular Lazy Load Demo source code
Top comments (16)
Given that we can lazy load components in their own, where is our use case to do so?
Right now, I feel like it doesn't fit well.
We mount the components to the ViewContainerRef, but do we not lose the ability to style where the lazy component gets mounted.
Take for example a page that contains a login form.
We don't necessarily want that visible until the user clicks a Sign In button on our page. Once we do this, then we lazy load the LoginFormComponent.
But if we're just mounting to a ViewContainerRef, how exactly do we style that the login form should maybe show in an in-line div under the Sign In button the user clicked?
Good questions. I planted the seeds of some of these same questions in the last section of this article. Perhaps lazy components can be most helpful when you need to load components dynamically. Which ones should load? Based on user roles? Based on some workflow or profile rules?
The login component you mention could contain its own styles.
I'll admit I do like the thought of having the ability in the future to dynamically load components based around more complex logic in our TS rather than bloating our HTML with it.
It paves the way for BuilderComponents that can generate views based on what information we pass to it.
Think of taking
FormBuilder
one step further toFormBuilderComponent
where we give it ourFormGroup
and it renders the appropriate form controls based on the structure of ourFormGroup
.My gripe right now is that, yes, our dynamically loaded component can style itself, but it does not currently have the ability to style where it lives/renders in its parent and its parent loses its ability to style where its dynamic children render.
With Content Projection using
ng-content
, we at least maintain the ability to decide where our children get rendered and how the area surrounded them gets styled.This ASP.NET-style school of thought was part of the original Angular 2 design doc, but features such as FormLayoutComponent never made the final cut, neither did built-in state management and quite a few other features.
I tried to use this approach to dynamically change which component is loaded with a selector, like this:
ng-template selector="my-component"
I know this is not a "you should do this" article, but...
I feel like the loss of declarative input/output is a bit limiting.
Take a look at NGX Dynamic. It could probably be combined with dynamic imports to get lazy-loaded components with property and event bindings.
Thank you for the suggestion, I had already checked npmjs.com/package/ng-dynamic-compo... which looks similar. Unfortunately, the declarative option in NGX Dynamic is deprecated.
To give you a bit more context on my "experiments": I am developing an app that needs to be customized by delivery teams. Customization will mostly be view changes. My idea is to put the biggest part of the code in an external npm module (so that updates/fixes are released via npm), then if the delivery team wants to change some component, they can simply load another component with the same selector.
Since delivery teams are usually composed of junior devs, I would really like to give them a view markup that is as "standard" as possible, hence my hope to do something like this (deprecated in NGX Dynamic):
<ng-template selector="my-component" [input]="myInput" (output)="myOutput()">
Anyway, I will probably end up putting the HTML views in the distributed code, not on npm, but dynamic component loading is a very interesting topic for me :)
Did you consider letting them pass in templates and rendering them using ngTemplateOutlet?
For people wondering whatβs the use case of lazy loaded components, a good example is a pdf file download button. Rather than preloading the client side pdf maker library, we can embed the pdf maker library in a lazy loaded component which gets instantiated on click of download button. This reduces the size of chunk file that renders the page and delays fetching of the library chunk file until it is actually used.
That's def a good use case for the apps we're building at work!
I came across this article that uses some newer web pack features and a service to achieve the same effect. It works great in an app with a more traditional "feature module" architecture. Maybe it's useful for you
medium.com/javascript-in-plain-eng...
John, we can use feature render modules to use child components in the template instead of this imperative approach. We should remember to pass the injector along to the embedded views to enable dependency injection.
An upcoming alternative to ViewContainerRef#createComponent is renderComponent.
Another way to switch from an imperative approach to a declarative approach is to use *ngComponentOutlet instead of ViewContainerRef#createComponent.
Here's an alternative take. Lazy-loaded routed components without Angular modules, using the Angular Router:
github.com/LayZeeDK/ngx-lazy-loade...
Cool - thanks for sharing!
I would really like to lazyload a component with my angular element. Don't think is possible but it would be so cool.