Angular - Dynamic components



Angular allows the component to be dynamically created and loaded at run time at a specific location in the host (another) component. Loading a component at run time opens a lot of opportunities to do advanced functionality. For example, a banner rotation component can accept heavily customized banner item instead of accepting banners confirming to a specific template, which is always pre-defined and static in nature.

Let us learn how to create an Angular component at run time and attach it to the host component in this chapter.

How to Render Angular Components Dynamically?

There are two ways to create dynamic components in Angular:

Using NgComponentOutlet

The NgComponentOutlet is a directive that helps in dynamic component creation. It instantiates a Component and embeds its view (host view) inside the current view. Please note it only accepts Components and to use it, you need to import CommonModule.

Example

In this example, we see the practical implementation of NgComponentOutlet directive. Here, we create an Angular application that will have two main child components and they will be associated with buttons named Admin and User. On clicking the Admin button, AdminBioComponent will be displayed and when you click the User button, StandardBioComponent will be displayed.

Step 1: Create AdminBioComponent using the given command −

ng g c admin-bio

Step 2: Create a second component, StandardBioComponent using the command given below −

ng g c standard-bio

Step 3: We need another component that will contain the logic of dynamic component creation.

ng g c check-bio

Step 4: Now, open admin-bio.component.html file and copy the below code −

<h3>Admin Bio</h3>
<p>Content of the Admin Bio Component.</p>

Step 5: Then, open standard-bio.component.html file and copy the below code −

<h3>Standard Bio</h3>
<p>Content of the Standard Bio Component.</p>

Step 6: Next, open check-bio.component.html file and copy the below code −

<ng-container *ngComponentOutlet="getBioComponent()"></ng-container>

Step 7: In the check-bio.component.ts file, create a method named getBioComponent(). This method will check which button is clicked among the two and display the view accordingly. The complete code is given below −

import { Component, Input } from '@angular/core';
import { AdminBioComponent } from '../admin-bio/admin-bio.component';
import { StandardBioComponent } from '../standard-bio/standard-bio.component';
import { NgComponentOutlet } from '@angular/common';

@Component({
  selector: 'app-check-bio',
  standalone: true,
  imports: [NgComponentOutlet],
  templateUrl: './check-bio.component.html',
  styleUrl: './check-bio.component.css'
})
export class CheckBioComponent {
  @Input() user!: {name: string, isAdmin: boolean};
  getBioComponent() { 
    console.log(this.user.isAdmin)
    return this.user.isAdmin ? AdminBioComponent : StandardBioComponent;
  }
}

Step 8: Next, open app.component.html file and copy the below code −

<!-- buttons to trigger admin() and userLog() methods -->
<button (click) = "admin()">Admin</button>
<button (click) = "userLog()">User</button>
<!-- conditional rendering -->
<div *ngIf="isValid">
  <!--input binding -->
  <app-check-bio [user]="user"></app-check-bio>
  <router-outlet />
</div>

Here, the given buttons will trigger the admin() and userLog() methods. The *ngIf directive conditionally includes the <div> element in the DOM if the isValid property is true. If it is false, the <div> and its contents are not rendered.

Step 10: Finally, inside the app.component.ts file, copy the below code −

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { CheckBioComponent } from './check-bio/check-bio.component';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CheckBioComponent, CommonModule],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'dynamicComponent';

  user = {"name": "Admin", "isAdmin": false}
  isValid = false;
  admin(){
    this.user.isAdmin = true;
    this.isValid = true;
  }
  userLog(){
    this.user.isAdmin = false;
    this.isValid = true;
  }
}

Output

dynamic component using NgComponentOutlet

Using ViewContainerRef

The second way of creating and rendering components dynamically is by using ViewContainerRef. Other components or directives use it to get a reference to a view container corresponding to their location in the DOM. In this way, ViewContainerRef helps to dynamically create and insert components at that specific location.

Example

Let us create a real time application to display a collection of employees in two different formats, table and gallery. We will create two components, one to show employees in the table and another in gallery format. Then we will create a host component, which will dynamically load either table based component or gallery based component.

Step 1: Create an interface to hold common data across multiple dynamic component

$ ng generate interface DynData
CREATE src/app/dyn-data.ts (29 bytes)

Step 2: Create a directive, DynSample. The purpose of the directive is to target and select the view container (where dynamic component will be placed) in the host component template.

$ ng generate directive DynSample
CREATE src/app/dyn-sample.directive.spec.ts (237 bytes)
CREATE src/app/dyn-sample.directive.ts (147 bytes)

Step 3: Create a dynamic component, DynList. The purpose of the component is to list the employee as gallery.

$ ng generate component DynList
CREATE src/app/dyn-list/dyn-list.component.css (0 bytes)
CREATE src/app/dyn-list/dyn-list.component.html (23 bytes)
CREATE src/app/dyn-list/dyn-list.component.spec.ts (567 bytes)
CREATE src/app/dyn-list/dyn-list.component.ts (209 bytes)

Step 4: Create a dynamic component, DynTable. The purpose of the component is to list the employees in tabular format.

$ ng generate component DynTable
CREATE src/app/dyn-table/dyn-table.component.css (0 bytes)
CREATE src/app/dyn-table/dyn-table.component.html (24 bytes)
CREATE src/app/dyn-table/dyn-table.component.spec.ts (574 bytes)
CREATE src/app/dyn-table/dyn-table.component.ts (213 bytes)

Step 5: Create a host component, DynHost. The purpose of the component is to host any one of the component, DynList and DynTable based on configuration.

$ ng generate component DynHost
CREATE src/app/dyn-host/dyn-host.component.css (0 bytes)
CREATE src/app/dyn-host/dyn-host.component.html (23 bytes)
CREATE src/app/dyn-host/dyn-host.component.spec.ts (567 bytes)
CREATE src/app/dyn-host/dyn-host.component.ts (209 bytes)

Step 6: Next, open DynSampleDirective and start updating the code. The code generated by angular CLI is as follows,

import { Directive } from '@angular/core';

@Directive({
   selector: '[appDynSample]'
})
export class DynSampleDirective {

   constructor() { }
}

Here,

  • Attribute directive purpose is to target the element (ng-template) in the host components template where the dynamic component will be injected.

  • appDynSample is the selector to target and select the element (ng-template). The dynamic component will be created, initialized and injected into the appHello element (ng-template) later.

Step 7: Next, initialize the view component reference object (ViewComponentRef) in the directive as shown below −

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
   selector: '[appDynSample]'
})
export class DynSampleDirective {

   constructor(public viewContainerRef: ViewContainerRef) { }

}

Here,

  • Imported the ViewContainerRef from @angular/core module

  • Initialized the viewContainerRef object through contructor dependency injection. viewContainerRef is the view container of the targeted element (ng-template) in the host components template. ViewContainerRef has method to dynamically create a component and append that component to it. We will use it later in the host component to dynamically create the DynListComponent and DynTableComponent.

Step 8: Next, open the interface, DynData and add an array property, data

export interface DynData {
   data : any[]
}

Step 9: Next, open the DynListComponent component and implement DynData interface.

import { Component } from '@angular/core';

import { DynData } from '../dyn-data'

@Component({
   selector: 'app-dyn-list',
   templateUrl: './dyn-list.component.html',
   styleUrls: ['./dyn-list.component.css']
})
export class DynListComponent implements DynData {
   data: any[] = []
}

Here,

  • Imported DynData interface and implements it in class definition

  • Included the data property as per DynData interface specification

Step 10: Next, open the components template and render the data as list of items

<div class="gallery">
   <div *ngFor="let item of data" class="card">
      <div class="container">
         <h4><b>{{ item.name }}</b></h4>
         <p>{{ item.role }}</p>
      </div>
   </div>
</div>

Here,

  • data hold the list of employees with two properties, name and role

  • Used ngFor to show the employees as list of cards

Step 11: Next, open the components styles and add necessary CSS as shown below −

.gallery {
   display: flex;
   flex-wrap: wrap;
   justify-content: left;
   gap: 10px;
}

.card {
   flex-basis: 200px;
   box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}

.card:hover {
   box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}

.container {
   padding: 2px 16px;
   max-width: 200px;
}

Here, we used flex feature of CSS to show the employees in gallery format

Step 12: Next, open the DynTableComponent component and implement DynData interface.

import { Component } from '@angular/core';

import { DynData } from '../dyn-data'

@Component({
   selector: 'app-dyn-table',
   templateUrl: './dyn-table.component.html',
   styleUrls: ['./dyn-table.component.css']
})
export class DynTableComponent implements DynData {
   data: any[] = []
}

Here,

  • Imported DynData interface and implements it in class definition

  • Included the data property as per DynData interface specification

Step 13: Next, open the components template and render the data as list of items

<table class="employees">
   <thead>
      <tr>
         <th>Name</th>
         <th>Role</th>
      </tr>
   </thead>
   <tbody>
      <tr *ngFor="let item of data">
         <td>{{ item.name }}</td>
         <td>{{ item.role }}</td>
      </tr>
   </tbody>
</table>

Here,

  • data hold the list of employees with two properties, name and role

  • Used ngFor to render the employees as rows in the html table

Step 14: Next, open the components styles and add necessary CSS as shown below −

.employees {
   border-collapse: collapse;
   width: 400px;
}

.employees td, .employees th {
   padding: 8px;
}

.employees tbody tr:nth-child(even){background-color: #f2f2f2;}

.employees tbody tr:hover {background-color: #ddd;}

.employees thead th {
   padding-top: 12px;
   padding-bottom: 12px;
   text-align: left;
   background-color: brown;
   color: white;
}

Step 15: Next, open DynHostComponent components template and include the DynSampleDirective as shown below −

<ng-templte appDynSample></ng-template>

Here, we have shown the ng-template using our DynSample directive.

Step 16: Next, open DynHostComponent and import necessary classes

import { Component, ViewContainerRef, OnInit, ViewChild, Input } from '@angular/core';

Step 17: Next, import interface, list component, table component and directive

import { DynData } from '../dyn-data'
import { DynSampleDirective } from '../dyn-sample.directive'
import { DynListComponent } from '../dyn-list/dyn-list.component'
import { DynTableComponent } from '../dyn-table/dyn-table.component'

Step 18: Implement OnInit life cycle hook in the class declaration

export class DynHostComponent implements OnInit {
}

Step 19: Declare an input property to get format information (table / list) from user.

@Input() format: string = 'list'

Step 20: Declare a property, data and set the sample data

private data = [
   {
      'name': 'John',
      'role': "Manager"
   },
   {
      'name': 'Peter',
      'role': "Marketing Intern"
   },
   {
      'name': 'Mary',
      'role': "Technical Intern"
   },
   {
      'name': 'Jack',
      'role': "Sales Manager"
   },
   {
      'name': 'Jessica',
      'role': "Delivery Head"
   },
]

Step 21: Declare host property and get the ng-template view component from the template by using @ViewChild and directive type, DynSampleDirective

@ViewChild(DynSampleDirective, {static: true}) host!: DynSampleDirective;

Step 22: Do actual implementation of dynamically creating the component and loading it into the appropriate place in the ngOnInit life cycle hook

ngOnInit() {
   const viewContainerRef = this.host.viewContainerRef;
   viewContainerRef.clear()
   
   if(this.format == 'table') {
      const compRef = viewContainerRef.createComponent<DynData>(DynTableComponent);
      compRef.instance.data = this.data;
   } else {
      const compRef = viewContainerRef.createComponent<DynData>(DynListComponent);
      compRef.instance.data = this.data;
   }
}

Step 23: The complete listing of the host component is as follows,

import { Component, ViewContainerRef, OnInit, ViewChild, Input } from '@angular/core';

import { DynData } from '../dyn-data'
import { DynSampleDirective } from '../dyn-sample.directive'
import { DynListComponent } from '../dyn-list/dyn-list.component'
import { DynTableComponent } from '../dyn-table/dyn-table.component'

@Component({
   selector: 'app-dyn-host',
   templateUrl: './dyn-host.component.html',
   styleUrls: ['./dyn-host.component.css']
})
export class DynHostComponent implements OnInit {
   @Input() format: string = 'table'
   
   private data = [
   {
      'name': 'John',
      'role': "Manager"
   },
   {
      'name': 'Peter',
      'role': "Marketing Intern"
   },
   {
      'name': 'Mary',
      'role': "Technical Intern"
   },
   {
      'name': 'Jack',
      'role': "Sales Manager"
   },
   {
      'name': 'Jessica',
      'role': "Delivery Head"
   },
   ]
   
   @ViewChild(DynSampleDirective, {static: true}) host!: DynSampleDirective;
   
   ngOnInit() {
      const viewContainerRef = this.host.viewContainerRef;
      viewContainerRef.clear()
      
      if(this.format == 'table') {
         const compRef = viewContainerRef.createComponent<DynData>(DynTableComponent);
         compRef.instance.data = this.data;
      } else {
         const compRef = viewContainerRef.createComponent<DynData>(DynListComponent);
         compRef.instance.data = this.data;
      }
   }
}

Step 24: Next, open the app components template and include the host component.

<app-dyn-host format="table" />

Here, we have directed the host component to render the employees data in the tabular format. The host component will dynamically create the DynTableComponent and inject into the host component template.

Step 25: Next, run the application and you can see that the employee data is shown in a tabular format as shown below −

employee data

Step 26: Next, open the app components template and change the format to gallery.

<app-dyn-host format="gallery" />

Here, we have directed the host component to render the employees data in gallery format. The host component will dynamically create the DynListComponent and inject into the host component template.

Step 27: Next, run the application and you can see that the employee data is shown in gallery format as shown below −

gallery format
Advertisements