0%

angular-dynamically-load-component

Introduction

In this article, we will learn how to dynamically load a component in Angular, In addition to load a component in html template directly, you can also load a component dynamically with NgComponentOutlet or ViewContainerRef

NgComponentOutlet

NgComponentOutlet is a directive that provide a declarative way for dynamic component creation, it requires a component type as input and create an instance of the component in the view.

1
*ngComponentOutlet="componentType"

Here are the code snippets to dynamically load a component with NgComponentOutlet:

1
2
<!-- app.component.html -->
<ng-container *ngComponentOutlet="getComponent()"></ng-container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app.component.ts
import { Component } from '@angular/core';
import {NgComponentOutlet} from "@angular/common";
import {ProductComponent} from "./product/product.component";
import {CustomerComponent} from "./customer/customer.component";

@Component({
selector: 'app-root',
standalone: true,
imports: [NgComponentOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
showProduct = true;

// We return the component type by condition.
getComponent() {
return this.showProduct? ProductComponent: CustomerComponent;
}
}

Question:
What’s the difference between *ngComponentOutlet and *ngIf* in this case? *ngIf can also load the component by condition.

ViewContainerRef

ViewContainerRef is a class that represents a container where one or more views can be attached to a component. It provides methods to create components and attach them to the container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import {Component, OnInit, ViewContainerRef} from '@angular/core';
import {ProductComponent} from "./product/product.component";
import {NgComponentOutlet} from "@angular/common";
import {CustomerComponent} from "./customer/customer.component";

@Component({
selector: 'app-root',
standalone: true,
imports: [NgComponentOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent implements OnInit {
showProduct = true;
constructor(private viewContainerRef: ViewContainerRef) {
}

ngOnInit() {
this.viewContainerRef.createComponent(this.getComponent());
}

getComponent() {
return this.showProduct? ProductComponent: CustomerComponent;
}
}

The above code will create the component instance as the sibling node of the host element(the component where ViewContainerRef was declared).

Lazy load component

You can also lazy load a component with ViewContainerRef.

1
2
3
<!-- app.component.html -->
<p>app works</p>
<button (click)="lazyLoadComponent()">Lazy load component</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// app.component.ts
import {Component, ViewContainerRef} from '@angular/core';
import {NgComponentOutlet} from "@angular/common";

@Component({
selector: 'app-root',
standalone: true,
imports: [NgComponentOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
showProduct = false;
constructor(private viewContainerRef: ViewContainerRef) {
}

async lazyLoadComponent() {
this.viewContainerRef.clear(); // avoid loading multiple components
const component = await this.getComponent();
this.viewContainerRef.createComponent(component);
}

async getComponent() {
if (this.showProduct) {
const {ProductComponent} = await import('./product/product.component');
return ProductComponent;
} else {
const {CustomerComponent} = await import('./customer/customer.component');
return CustomerComponent;
}
}
}

Explanation: The above code decides which component should be loaded when the user clicks the button, based on the showProduct variable. After running the program, press F12 to open the developer tools, and observe the Network tab. You can see that after clicking the Lazy load component button, the app initiates a request to dynamically load the Product component.

angular-lazy-load-component

The component was bundled separately with name chunk-VFVWUJZL.js to support lazy load, if you want to see the real name of the chunk file, you can add namedChunk to your angular.json file.

1
2
3
4
5
6
7
8
9
10
11
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/my-app",
"index": "src/index.html",
// ...
"namedChunks": true // <-- here
},
}
}

In this way the chunk file will got a name like product.component-RGAR2EGQ.js

Finally, the getComponent method can be optimized to avoid duplicate code, but it seems that the readability is not very good after optimization. What do you think?

优化后的代码:

1
2
3
4
5
async getComponent() {
const component = this.showProduct ? 'ProductComponent' : 'CustomerComponent';
const result = await import(`./${component.toLowerCase()}/${component}.component`);
return result[component];
}

References

[1] programmatic-rendering