DEV Community

loading...
Cover image for Angular 9: Lazy Loading Components
Angular

Angular 9: Lazy Loading Components

john_papa profile image John Papa Originally published at johnpapa.net ・5 min read

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.

Lazy Loading Components in Angular 9

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:

  1. Dynamic Component Loader
  2. 7 new features in Angular 9.
  3. VS Code editor
  4. Angular Essentials Extension for VS Code
  5. Angular Language Service for VS Code
  6. Angular Lazy Load Demo source code

Discussion (16)

Collapse
coly010 profile image
Colum Ferry

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?

Collapse
john_papa profile image
John Papa Author

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.

Collapse
coly010 profile image
Colum Ferry • Edited

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 to FormBuilderComponent where we give it our FormGroup and it renders the appropriate form controls based on the structure of our FormGroup.

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.

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen • Edited

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.

Collapse
filini profile image
Filippo Gualandi • Edited

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.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Take a look at NGX Dynamic. It could probably be combined with dynamic imports to get lazy-loaded components with property and event bindings.

Collapse
filini profile image
Filippo Gualandi

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 :)

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen

Did you consider letting them pass in templates and rendering them using ngTemplateOutlet?

Collapse
zombierobo profile image
Hasmukh Suthar

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.

Collapse
vitale232 profile image
Andrew Vitale

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

Collapse
layzee profile image
Lars Gyrup Brink Nielsen • Edited

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.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen • Edited

Another way to switch from an imperative approach to a declarative approach is to use *ngComponentOutlet instead of ViewContainerRef#createComponent.

Collapse
Sloan, the sloth mascot
Comment deleted
Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Here's an alternative take. Lazy-loaded routed components without Angular modules, using the Angular Router:
github.com/LayZeeDK/ngx-lazy-loade...

Collapse
john_papa profile image
John Papa Author

Cool - thanks for sharing!

Collapse
bergermarko profile image
Marko Berger

I would really like to lazyload a component with my angular element. Don't think is possible but it would be so cool.

Forem Open with the Forem app