Wednesday, 12 June 2019

How to Dynamically load a Component in Angular

In this article, we will learn how to load a component dynamically. In various scenarios, you may need to load a component dynamically.
Mainly, in the component template, a component is loaded using the component selector that is identified at angular compile time. The component can also be loaded dynamically at runtime with the help of ComponentFactoryComponentFactoryResolver, and ViewContainerRef.
Those components which need to be loaded dynamically must also be configured inentryComponents metadata of @NgModule decorator in the module file. To load a dynamic component in a template we required an insert location and to get it we need ViewContainerRef of a decorator or a component.
ComponentFactory and ComponentFactoryResolver
ComponentFactory is used to create an instance of components where ComponentFactoryResolver resolves a ComponentFactory for a particular component. It is used as follows.

CODE EXPRESSION

let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component); 
ViewContainerRef
ViewContainerRef represents a container where we can attach one or more views to a component and also show an API to create components. Some important methods of ViewContainerRef are createEmbeddedView(),clear(),get() ,insert(),move()createComponent() etc.
CreateEmbeddedView() instantiates an embedded view and inserts it into this container.
createComponent() instantiates a single component and inserts its host view into the this container at a specified index.
In dynamic component loader example, we will load component using createComponent() of ViewContainerRef.

CODE EXPRESSION

let componentRef = viewContainerRef.createComponent(componentFactory);
We get a ComponentRef of the newly created component as a return of the above method.
clear() method of ViewContainerRef destroys all existing views in the container.
Now, we will create 2 components as listed below with below CLI command, which we will load dynamically on change of dropdown.
ng g c studentinfo
ng g c parentinfo

STUDENTINFO COMPONENT

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

@Component({
  selector: 'app-student-info',
  templateUrl: './student-info.component.html',
  styleUrls: ['./student-info.component.css']
})
export class StudentInfoComponent implements OnInit {
  message: string;
  constructor() { }

  ngOnInit() {
    alert(this.message);
  }
}

PARENTINFO COMPONENT

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

@Component({
  selector: 'app-parent-info',
  templateUrl: './parent-info.component.html',
  styleUrls: ['./parent-info.component.css']
})
export class ParentInfoComponent implements OnInit {

  message: string;
  constructor() { }

  ngOnInit() {
    alert(this.message);
  }

}
To load StudentInfoComponent and ParentInfoComponent dynamically we need a container. If we want to load StudentInfoComponent and ParentInfoComponent inside AppComponent, we require a container element in the AppComponent.
The template for AppComponent.html is as below in which a dropdown is avaliabe :

APP COMPONENT

import { Component } from '@angular/core';
import { StudentInfoComponent } from './student-info/student-info.component';
import { ParentInfoComponent } from './parent-info/parent-info.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
   data = [
    {
      "Id": 1,
      "Name": "Student Info"
    },
    {
      "Id": 2,
      "Name": "Parent Info"
    }
  ]
  selectName(id : number) {
 
  }

}
As we have seen, there is an entry point template or a container template in which we will load StudentInfoComponent and ParentInfoComponent dynamically.
In AppComponent, we need to import the following:
  • ViewChild, ViewContainerRef, and ComponentFactoryResolver from @angular/core.
  • ComponentRef and ComponentFactory from @angular/core.
  • StudentInfoComponent from student-info.component.
  • ParentInfoComponent from parent-info.component.
After importing the required elements, app.component.ts will look like as the following :

APP.COMPONENT.TS

import {
  Component,
  ViewChild,
  ViewContainerRef,
  ComponentFactoryResolver,
  ComponentRef,
  ComponentFactory
} from '@angular/core';
import { StudentInfoComponent } from './student-info/student-info.component';
import { ParentInfoComponent } from './parent-info/parent-info.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
  data = [
    {
      "Id": 1,
      "Name": "Student Info"
    },
    {
      "Id": 2,
      "Name": "Parent Info"
    }
  ]
  selectName(id : number) {

  }
}
Inside the Component class, we can access template using ViewChild. The template is a container in which we load the component dynamically. Therefore, we have to access the template with the ViewConatinerRef where ViewContainerRef represents a container where one or more views can be attached. This can contain two types of views.
Embedded views are created by creating an instance of TemplateRef using the createEmbeddedView() method
Host Views are created by creating an instance of a component using the createComponent() method. Host Views is used to dynamically load StudentInfoComponent and ParentInfoComponent.
Let's create a variable called entry which will refer to the template element. In addition, we have also injected ComponentFactoryResolver services to the component class, which will be required to dynamically load the component.

APP.COMPONENT.TS

export class AppComponent {
  title = 'app';
  componentRef: any;
  @ViewChild('loadComponent', { read: ViewContainerRef }) entry: ViewContainerRef;
  constructor(private resolver: ComponentFactoryResolver) { }
}
Note that the entry variable, which is a reference to a template element has an API to create components, destroy components, etc.
To create a component, first, create a function. Within the function, we must perform the following tasks:
  • Clear the container.
  • Create a factory for StudentInfoComponent and ParentInfoComponent.
  • Create a component using the factory.
  • Pass the value for message variable using a component reference instance method.
keep everything in one place, The createComponent function will look like this:

APP.COMPONENT.TS

createComponent(Id: number) {
    this.entry.clear();
    if (Id == 1) {
      const factory = this.resolver.resolveComponentFactory(StudentInfoComponent);
      this.componentRef = this.entry.createComponent(factory);
    } else if (Id == 2) {
      const factory = this.resolver.resolveComponentFactory(ParentInfoComponent);
      this.componentRef = this.entry.createComponent(factory);
    } 
    this.componentRef.instance.message = "Called by appComponent";
  }
We can call the createComponent function on change event of dropdown and internally this method will call the create() method from the factory and will append the component as a sibling to our container.
While running the application, we will get an error because we have not set the entryComponents in AppModule. We can set this as shown below:

APP.MODULE.TS

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { StudentInfoComponent } from './student-info/student-info.component';
import { ParentInfoComponent } from './parent-info/parent-info.component';

@NgModule({
  declarations: [
    AppComponent,
    StudentInfoComponent,
    ParentInfoComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  entryComponents: [StudentInfoComponent,ParentInfoComponent]
})
export class AppModule { }
In the output, we see that component is getting loaded dynamically on the selection of the dropdown.
SahosoftTutorials-Dynamic-Component
As we change the dropdown value, the component will be reloaded with a different component. A component can be destroyed using the destroy() method on the componentRef.

APP.COMPONENT.TS

destroyComponent() {
    this.componentRef.destroy();
}
We can manually destroy a dynamically loaded component by calling the function or by placing it within the ngOnDestroy() life cycle hook of the component so that the dynamically loaded component will also be destroyed when the host component is destroyed.
keeping everything together, AppComponent will look like as shown below:

APP.COMPONENT.TS

import {
  Component,
  ViewChild,
  ViewContainerRef,
  ComponentFactoryResolver,
  ComponentRef,
  ComponentFactory
} from '@angular/core';
import { StudentInfoComponent } from './student-info/student-info.component';
import { ParentInfoComponent } from './parent-info/parent-info.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
  componentRef: any;
  @ViewChild('loadComponent', { read: ViewContainerRef }) entry: ViewContainerRef;
  constructor(private resolver: ComponentFactoryResolver) { }
  createComponent(Id: number) {
    this.entry.clear();
    if (Id == 1) {
      const factory = this.resolver.resolveComponentFactory(StudentInfoComponent);
      this.componentRef = this.entry.createComponent(factory);
    } else if (Id == 2) {
      const factory = this.resolver.resolveComponentFactory(ParentInfoComponent);
      this.componentRef = this.entry.createComponent(factory);
    } 
    this.componentRef.instance.message = "Called by appComponent";
  }
  destroyComponent() {
    this.componentRef.destroy();
  }
  data = [
    {
      "Id": 1,
      "Name": "Student Info"
    },
    {
      "Id": 2,
      "Name": "Parent Info"
    }
  ]
  selectName(id : number) {
    this.createComponent(id);
  }
}

No comments:

Post a Comment