2 Simple Examples to understand how Dynamic components work

AngularEnthusiast
7 min readApr 15, 2020

The concept of dynamic components is adding components dynamically at run time. Is it so simple?:)

Many confuse lazy loading of modules with dynamic loading of components.

The purpose of lazy loading is to improve the performance of the application by loading a module only when a certain route/path is invoked. Until then the module is not needed.

Components can be dynamically loaded in a lazily loaded module or in a module that is loaded when the application is bootstrapped.

Another point to note here is that usually a component is loaded based on a route, but in case of dynamically loading components, there are no routes involved for each of the components getting loaded.

Example 1:

Lets take a simple example of a dynamic component gets loaded.

Consider a component ComponentA, in which we shall load a ComponentB dynamically.

Component A template:

<button (click)="addComponent()">Generate Component</button>
<ng-container #ref></ng-container>

Our dynamic component ComponentB will be loaded within ng-container when the button is clicked.

Its important to understand the difference between <ng-template> and <ng-container> first.

ng-container is useful for grouping elements. It doesnt interfere with styles or layout because angular doesnt add it to the DOM. It is a syntax element functioning similar to the braces in if block in JS. The braces indicate the group of code to be executed.

Multiple structural directives cannot be used on a single element. For instance, we cannot use *ngIf and *ngFor together on the same element.The effect can be quite unpredictable.

Instead we could go for an arrangement like below:

<select [(ngModel)]=”hero”> 
<ng-container *ngFor=”let h of heroes”>
<ng-container *ngIf=”showSad || h.emotion !== ‘sad’”>
<option [ngValue]=”h”>{{h.name}} ({{h.emotion}})</option>
</ng-container>
</ng-container>
</select>

We could have used additional div element instead of <ng-container> but that would just add unnecessary html to the DOM.

In structural directives like *ngIf and *ngFor, there is a signficance to * before it. * is a symbol representing something more complex

<div *ngIf=”hero”>{{name}}</div>

Here, the host element is the div element. Angular translates the above html into:

<ng-template [ngIf]=”hero”> <div>{{name}}</div> </ng-template>

<ng-template> is wrapped around the host element and the *ngIf on the host element is transformed into a property binding on the <ng-template>.

Final result on the DOM is:

So we understand that Angular internally uses <ng-template> for rendering html. Before actually rendering the view it replaces <ng-template> and its contents with a comment as shown above.

If you explicitly enclose any content inside <ng-template>,it wont be visible in the view.

TemplateRef can be used to access the contents of <ng-template>. This is very useful when we create our own custom structural directives.

Component A Class:

export class ComponentA {@ViewChild(‘ref’,{read:ViewContainerRef})ref:ViewContainerRef;index:number=0;constructor(private resolve:ComponentFactoryResolver) { }addComponent(){let Compfactory=this.resolve.resolveComponentFactory(ComponentB); this.ref.clear();let componentRef= this.ref.createComponent(Compfactory);componentRef.instance.counter=this.index++; }
}

Before I explain ComponentA class, have a look at ComponentB class. It helps understanding why we are doing certain things.

export class ComponentB{constructor() { }ngOnInit() {}@Input()counter:number;}

Component B Template:

<p>Dynamic Component {{counter}} loaded</p>

Another essential step is to add ComponentB to the list of entryComponents in the @ NgModule definition.

@NgModule({
declarations: [ComponentA, ComponentB],
imports: [CommonModule,RouterModule.forChild(routes)],
entryComponents:[ComponentB]
})

Now lets understand what is happening in ComponentA class.

We are accessing <ng-container> using the reference variable ref in the @ViewChild.

What is a ViewContainerRef? As the name suggests,it is a container that can contain 1 or more views that are attached to the component.

The view can be a host view or an embedded view. Both the views can be created using methods of the reference ref.

An Embedded view is created by passing the TemplateRef to the method createEmbeddedView() of reference ref.

In Angular every component is created from a factory. Factories are generated by the compiler using the data you supply in the @ Component decorator.

ComponentFactory is the base class for a factory that can create a component dynamically.

ComponentFactoryResolver maps the Components to the ComponentFactory classes.

A host view is created by passing the ComponentFactory to the createComponent() of the reference ref.

When I click on the button, the addComponent() is called. This is what is happening inside the method:

let Compfactory=this.resolve.resolveComponentFactory(ComponentB); this.ref.clear();let componentRef= this.ref.createComponent(Compfactory);componentRef.instance.counter=this.index++; 

resolve is the reference of the ComponentFactoryResolver created inside the constructor. We are calling the method resolveComponentFactory() on this resolve reference and passing the component ComponentB as argument to it.

This method returns the factory that created ComponentB

clear() will destroy any existing views of the component in the view container.

The factory Compfactory is returned from the method resolveComponentFactory().

Compfactory is passed as argument to createComponent() to create an instance of ComponentB and insert its host view in the view container.

The instance of ComponentB created:componentRef can be used to access the properties and methods of the component.

We are assigning the Input counter property of ComponentB to the incremented value of index.

On the first button click, “Dynamic Component 0 loaded” will be displayed.

index increments with every button click. Also note that the clear() will remove a dynamic component before creating a new one.

Thus on the 2nd button click,“Dynamic Component 0 loaded” will be replaced by “Dynamic Component 1 loaded”.

If you want to append the dynamic components created, one after another, then remove the below line of code from addComponent().

this.ref.clear();

Example 2:

This is a slightly more complex example than the previous one. It is similar to the one provided in the Angular documentation but has been modified a bit.

We are just going to flash a list of ToDo items a user has created and also show its completion status.

Flashing a list of ToDo items is very unusual. Ads,weather reports,news feeds are more commonly flashed.

The project uses 3 components:

ComponentA in which the other 2 components will be dynamically loaded.

ComponentB and ComponentC will have the ToDo item list details.

There are 2 interfaces:ListItem and ToDoItem to create a model of the data.

There is also a service ServiceA to return the ToDo List data to ComponentA.

Lets begin with the service and the data set.

export class ServiceA {
constructor() { }
ItemList:ToDoItem[]=[
{
component:ComponentB,data:{
title:”Your ToDoList”
}
},
{
component:ComponentC,data:{
title: “delectus aut autem”,
completed: false
}
},
{
component:ComponentC,data:{
title: “quis ut nam facilis et officia qui”,
completed: false
}},
{
component:ComponentC,
data:{
title: “et porro tempora”,
completed: true
}},
{
component:ComponentC,
data: {
title: “veritatis pariatur delectus”,
completed: true
}},
{
component:ComponentC,
data: {
title: “nesciunt totam sit blanditiis sit”,
completed: false
}},
{
component:ComponentC,
data:{
title: “laborum aut in quam”,
completed: true
}}
]getToDos()
{
return this.ItemList;
}}

ItemList is array of todo list items. Each item as you can see is an object with 2 properties:component and its corresponding data.

The idea here is to specify which component needs to be loaded and what data needs to be loaded in that component. ComponentB just tells us about what data is going to be displayed.ComponentC flashes the actual list item data.

We have a method getToDos() which returns the data to ComponentA.

ItemList is of type ToDoItem. ToDoItem is just an interface. Lets see what it has.

import { Type } from ‘@angular/core’;export interface ToDoItem
{
component:Type<any>,
data:ListItem;
}
export interface ListItem
{
title:string;
completed?:boolean;
}

Now lets check ComponentB and ComponentC which are quite self-explanatory. Both these components are loaded as child components inside ComponentA. ComponentA retrieves data from the service and passes the data to the child components to be displayed.

ComponentB class:

export class ComponentB{
constructor() { }
@Input()data:ToDoItem;
}

ComponentB Template:

<div>
<p>{{data.title}}</p>
</div>

ComponentC Class:

export class ComponentC {
constructor() { }
@Input() data:ToDoItem;
}

ComponentC template:

<div><h4>Title</h4><p>{{data.title}}</p><h4>Completed</h4><p *ngIf=”data.completed”>Yes</p><p *ngIf=”!data.completed”>No</p></div>

Finally lets get to ComponentA:

ComponentA template:

<ng-container #ref></ng-container>

ComponentA class:

export class ComponentA {@ViewChild(‘ref’,{read:ViewContainerRef})ref:ViewContainerRef;index:number=0;
toDoData:ToDoItem[];
clear:any;
constructor(private resolve:ComponentFactoryResolver,private serv:ServiceA) { }ngOnInit()
{
this.toDoData=this.serv.getToDos();
this.clear=setInterval(()=>{
this.setComponent();
},3000)
}
setComponent(){
if(this.index <this.toDoData.length)
{
let factory=this.resolve.resolveComponentFactory(this.toDoData[this.index].component);
this.ref.clear();let componentRef= this.ref.createComponent(factory); componentRef.instance.data=this.toDoData[this.index].data;this.index++;
}
else
{
clearInterval(this.clear);
}
}}

Once the component loads,we are first calling the service to retrieve the data and store it in toDoData.

Next,we are calling a method setComponent() every 3 secs.

We have set index to 0. This variable holds the index of each item in toDoData.

In the method,we are first checking if the index is less than the length of toDoData. If yes, we access the item corresponding to index value in toDoData and use to dynamically load the ComponentB or ComponentC with data.

let factory=this.resolve.resolveComponentFactory(this.toDoData[this.index].component);

In the above line, we are passing the component property of the list item to the resolveComponentFactory() to return the factory that created the component. The component could be ComponentB or ComponentC.

Next we clear any existing views in the view container.

this.ref.clear();

After that we pass the returned factory to createComponent() to create an instance of the component(ComponentB or ComponentC)and insert its view in the view container.

This method also returns a reference componentRef of the component (ComponentB or ComponentC). We are assigning the data property corresponding to index of the list item to the @ Input() data property of ComponentB or ComponentC.

let componentRef= this.ref.createComponent(factory);componentRef.instance.data=this.toDoData[this.index].data;

This way ComponentB or C will use the data in their template.

Please let me know your comments:)

--

--

AngularEnthusiast

Loves Angular and Node. I wish to target issues that front end developers struggle with the most.