Lazy loading components with Angular 9+ just got a lot easier!
Previous to Angular v9
Previously, lazy loading a single component (not a lazy loaded module) in Angular took a bit of understanding the underlying APIs of Angular, and some might say, a bit of magic.
Angular version 9 introduced a new rendering engine called “Ivy”. This replaces the previous rendering engine, which was called “View Renderer”. With Ivy the APIs for Angular are simplified, making it much easier to compile components using ahead-of-time (AOT) compilations and to then lazily, and dynaically, load and create components during runtime.
Before Ivy and Angular version 9 we had to:
- Use the
NgModuleFactoryLoaderto load and compile the module, providing it an instance of the dependencyInjector. - We then use the
resolveComponentFactorymethod of an injectedComponentFactoryResolverproviding it with our component instance. - Finally, we can create and render the component via the
createComponent()method ofViewContainerRefin the DOM.
For the sake of brevity, I am not going to take a deep dive into the complexities of the code required for lazy load a component with Angular v2 to v8. Instead, I would refer you to the popular hero-loader module built by the team at HeroDevs.
Repository
In order to follow along, you need to checkout the v9-lazy-components branch of the repository:
git clone https://github.com/blove/angular-v9.git
git checkout v9-lazy-components
Demo

Existing Declarative Component
In this example, I currently have a PlanetComponent instance that displays the details associated with a Planet.
I use this component in order to display a person’s home planet.
For example, Luke Skywalker’s home planet it Tatooine.
Here is the template where I am currently implementing the PlanetComponent, located at src/app/features/people/dialogs/person-home-planet-dialog/person-home-planet-dialog.component.html:
<h1 mat-dialog-title>{{ data.person.fields.name }}: Home Planet</h1>
<mat-dialog-content>
<swr-planet [planet]="planet | async"></swr-planet>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Close</button>
</mat-dialog-actions>
And, here is the PlanetComponent class, which is located at src/app/features/people/presenters/planet/planet.component.ts:
@Component({
selector: 'swr-planet',
templateUrl: './planet.component.html',
styleUrls: ['./planet.component.scss'],
})
export class PlanetComponent {
/** The planet to display. */
@Input() planet: Planet;
}
The PlanetComponent has a single input that accepts the Planet to display.
Lazy Load Component with Angular v9
For the purpose of this exercise I want to lazy load the PlanetComponent.
First, we need to remove the existing declared component, and we’ll replace this with an <ng-template> element:
<h1 mat-dialog-title>{{ data.person.fields.name }}: Home Planet</h1>
<mat-dialog-content>
<ng-template></ng-template>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Close</button>
</mat-dialog-actions>
Next, we’ll access the ViewContainerRef associated with the <ng-container> using the @ViewChild() decorator in our component class.
This is located in src/app/features/people/dialogs/person-home-planet-dialog/person-home-planet-dialog.component.ts:
export class PersonHomePlanetDialogComponent {
/** The view container reference for the planet template. */
@ViewChild(TemplateRef, { read: ViewContainerRef })
private planetTemplateViewContainerRef: ViewContainerRef;
}
A few things to note:
- First, we use the
@ViewChild()decorator in order to create a view query for theTemplateRefinstance in our template, which we declared using the<ng-template>element. - Second, we specify the
optsoptional second argument to the@ViewChild()decorator function. This optional argument is an object, and one of the properties that we can specify is thereadproperty. - The
readproperty enables us to specify a different token to return from the view query. In this case, I want a reference to theViewContainerRefinstance. - We’ll use the
planetTemplateViewContainerRefclass property shortly to instruct the rendering engine where to render our lazy loaded component.
The next step is to inject an instance of the ComponentFactoryResolver class.
export class PersonHomePlanetDialogComponent {
/** The view container reference for the planet template. */
@ViewChild(TemplateRef, { read: ViewContainerRef })
private planetTemplateViewContainerRef: ViewContainerRef;
constructor(
private readonly componentFactoryResolver: ComponentFactoryResolver,
) {}
}
According to the documentation, the ComponentFactoryResolver is:
A simple registry that maps Components to generated ComponentFactory classes that can be used to create instances of components.
The next steps are:
- Lazy load the component bundle using the dynamic
import()function. This function requires the path to the module and returns aPromise, that when resolved, provides the module. - Use the
ComponentFactoryResolverto first get theComponentFactory. - Then we’ll use the
createComponent()method of ourViewContainerRefto create and render the component in the DOM. - Finally, we can use the returned
ComponentRefinstance to modify the properties of the instance.
export class PersonHomePlanetDialogComponent {
/** The view container reference for the planet template. */
@ViewChild(TemplateRef, { read: ViewContainerRef })
private planetTemplateViewContainerRef: ViewContainerRef;
constructor(
private readonly componentFactoryResolver: ComponentFactoryResolver,
) {}
private lazyLoadPlanet(planet: Planet): void {
import('../../presenters/planet/planet.component').then(
({ PlanetComponent }) => {
const component =
this.componentFactoryResolver.resolveComponentFactory(
PlanetComponent,
);
const componentRef =
this.planetTemplateViewContainerRef.createComponent(component);
componentRef.instance.planet = planet;
},
);
}
}
Let’s quickly review the lazyLoadPlanet() method:
- First, we use the
import()function to dynamically import a module (ES6 module, not NgModule). - This returns a
Promisethat resolves with the module. - I’m using object destructuring to access the
PlanetComponentclass that is exported in the module. - Use the
resolveComponentFactory()method of the injectedComponentFactoryResolverclass to get theComponentFactoryinstance for thePlanetComponent. - Use the
createComponent()method of theplanetTemplateViewContainerRefto create and render the component, which is appended toViewContainerRef. - The
createComponent()method returns aComponentRefinstance, which has aninstanceproperty that refers to the component instance. - Finally, we specify the
planetproperty of ourPlanetComponentinstance to provide the necessary data for display the information of theplanet.
Here is the complete source code that includes fetching the Planet for the person:
export class PersonHomePlanetDialogComponent implements OnDestroy, OnInit {
/** The view container reference for the planet template. */
@ViewChild(TemplateRef, { read: ViewContainerRef })
planetTemplateViewContainerRef: ViewContainerRef;
/** Unsubscribe from observable streams when the component is destroyed. */
private unsubscribe = new Subject();
constructor(
private readonly componentFactoryResolver: ComponentFactoryResolver,
@Inject(MAT_DIALOG_DATA) public data: { person: Person },
private readonly planetService: PlanetService,
) {}
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
ngOnInit() {
this.planetService
.getPlanetForPerson(this.data.person)
.pipe(
tap((planet) => this.lazyLoadPlanet(planet)),
takeUntil(this.unsubscribe),
)
.subscribe();
}
private lazyLoadPlanet(planet: Planet): void {
import('../../presenters/planet/planet.component').then(
({ PlanetComponent }) => {
const component =
this.componentFactoryResolver.resolveComponentFactory(
PlanetComponent,
);
const componentRef =
this.planetTemplateViewContainerRef.createComponent(component);
componentRef.instance.planet = planet;
},
);
}
}
And that’s how we lazy load a component using Angular version 9!
Lazy Loading Multiple Components into a Template
In the example above you might have noticed that we are using the TemplateRef query with @ViewChild() to access the <ng-template> template ref.
If you are lazy loading multiple components into a template this strategy will not work.
The solution here is to use multipe <ng-container> elements with template reference variables:
<ng-container #personContainer></ng-container>
<ng-container #planetContainer></ng-container>
I like to use the <ng-container> element for this as to not pollute the DOM with <div> (or other) elements.
Then, I can specify the template reference variable name to the @ViewChild() query to obtain the ViewContainerRef for each container:
export class PersonHomePlanetDialogComponent {
/** The view container reference for the person container. */
@ViewChild('personContainer', { read: ViewContainerRef })
private personViewContainerRef: ViewContainerRef;
/** The view container reference for the planet container. */
@ViewChild('planetContainer', { read: ViewContainerRef })
private planetViewContainerRef: ViewContainerRef;
}
Now I can lazy load components as necessary, and then create and render the appropriate component into the appropriate ViewContainerRef.
Dependencies
What if your lazy loaded component has dependencies that need to be injected into the component class’s constructor() function?
The answer is:
- If the dependency is already guaranteed to be loaded via the
providersarray in an@NgModule(), or via theprovidedInproperty in class’s@Injectable()decorator, then it should just work. Of course, if the dependency has not been provided, then dependency injection will fail with the error:NullInjectorError: No provider for XyzService!. - If the dependency has not already been provided then we can specify the
providersarray in the@Component()metadata of the lazy loaded component.
Here is an example of using the providers property in the component metadata to specify a dependency for a lazy loaded component:
@Component({
selector: 'swr-planet',
templateUrl: './planet.component.html',
styleUrls: ['./planet.component.scss'],
providers: [FilmService],
})
export class PlanetComponent {
/** The planet to display. */
@Input() planet: Planet;
constructor(filmService: FilmService) {}
}
Note:
- Using the
providersproperty in the@Component()decorator we can instruct dependency injection to create a provider for this component (and at this level of the dependency tree). - We can now inject the dependency via the component class’s
constructor()function.
Modules
What if your lazy loaded component relies on other modules?
For example, what if you need the FormsModule and/or the ReactiveFormsModule if your component is a form?
If we attempt to use NgForm in our template we are going to get an error: error: No directive found with exportAs 'ngForm'. The problem is that our component template relies on the publicly exported directives within theFormsModule.
The answer is that we include an NgModule in the same file as the component and specify the necessary imports for the component:
@Component({
selector: 'swr-planet',
templateUrl: './planet.component.html',
styleUrls: ['./planet.component.scss'],
})
export class PlanetComponent {
/** The planet to display. */
@Input() planet: Planet;
}
@NgModule({
declarations: [PlanetComponent],
imports: [FormsModule, ReactiveFormsModule],
})
class PlanetComponentModule {}
Note:
- The
PlanetComponentrelies on both theFormsModuleand theReactiveFormsModule. - We create an
NgModulein the same file as the lazy loaded component. - We specify the
declarationproperty with a reference to the component class; in the case thePlanetComponentclass. - We specify the
importsproperty with the array of modules that need to be imported for the lazy loaded component.
Conclusion
With Angular version 9 and the new Ivy compilation and rendering pipeline we can lazy load, create and render Angular components. 👏👏👏