Loading Angular Components Dynamically

John Au-Yeung - Jan 23 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to load and display Angular components dynamically.

Dynamic Component Loader

We can load components by using the ComponentFactoryResolver to load multiple dynamic components.

Also, we have to make a directive to host the components.

First, we have to create the following components and directives by running the following commands:

ng g directive BannerHost  
ng g component Foo  
ng g component Bar
Enter fullscreen mode Exit fullscreen mode

Then we write the following code:

app.module.ts :

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';  
import { AppComponent } from './app.component';  
import { FooComponent } from './foo/foo.component';  
import { BarComponent } from './bar/bar.component';  
import { BannerHostDirective } from './banner-host.directive';

@NgModule({  
  declarations: [  
    AppComponent,  
    FooComponent,  
    BarComponent,  
    BannerHostDirective  
  ],  
  imports: [  
    BrowserModule,  
    AppRoutingModule  
  ],  
  providers: [],  
  bootstrap: [AppComponent],  
  entryComponents: [  
    FooComponent,  
    BarComponent,  
  ]  
})  
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

banner-host.directive.ts :

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

@Directive({  
  selector: '[banner-host]'  
})  
export class BannerHostDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}
Enter fullscreen mode Exit fullscreen mode

bar.component.html :

bar
Enter fullscreen mode Exit fullscreen mode

foo.component.ts :

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

@Component({  
  selector: 'app-foo',  
  templateUrl: './foo.component.html',  
  styleUrls: ['./foo.component.css']  
})  
export class FooComponent implements OnInit {  
  @Input() data: string; constructor() { } 

  ngOnInit() {  
  }
}
Enter fullscreen mode Exit fullscreen mode

foo.component.html :

foo {{data}}
Enter fullscreen mode Exit fullscreen mode

app.component.ts :

import { Component, ComponentFactoryResolver, ViewChild } from '@angular/core';  
import { FooComponent } from './foo/foo.component';  
import { BannerHostDirective } from './banner-host.directive';  
import { BarComponent } from './bar/bar.component';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.css']  
})  
export class AppComponent {  
  interval: any;  
  currentBannerIndex: number = -1;  
  banners = [FooComponent, BarComponent]  
  @ViewChild(BannerHostDirective, { static: true }) bannerHost: BannerHostDirective;  

  constructor(private componentFactoryResolver: ComponentFactoryResolver) { } 

  ngOnInit() {  
    this.loadComponent();  
    this.getBanners();  
  } ngOnDestroy() {  
    clearInterval(this.interval);  
  }

  loadComponent() {  
    this.currentBannerIndex = (this.currentBannerIndex + 1) % this.banners.length;  
    const bannerItem = this.banners[this.currentBannerIndex];
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(bannerItem);  
    const viewContainerRef = this.bannerHost.viewContainerRef;  
    viewContainerRef.clear();  
    const componentRef = viewContainerRef.createComponent(componentFactory);  
    if (componentRef.instance.constructor === FooComponent) {  
      (<FooComponent>componentRef.instance).data = 'hello';  
    }  
  } 

  getBanners() {  
    this.interval = setInterval(() => {  
      this.loadComponent();  
    }, 3000);  
  }  
}
Enter fullscreen mode Exit fullscreen mode

app.component.html :

<ng-template banner-host></ng-template>
Enter fullscreen mode Exit fullscreen mode

In the code above, we created the banner-host directive to inject the component that we’re going to load.

We applied that to ng-template in app.component.html .

FooComponent takes a data input that we’re going to set.

Then the components are loaded in AppComponent .

The loadComponent method in AppComponent by going through the banners that we defined in the banners array.

We changed the currentBannerIndex every 3 seconds as indicated by the getBanners method.

The actual component loading is done by the following code:

const componentFactory = this.componentFactoryResolver.resolveComponentFactory(bannerItem);  
const viewContainerRef = this.bannerHost.viewContainerRef;  
viewContainerRef.clear();  
const componentRef = viewContainerRef.createComponent(componentFactory);  
if (componentRef.instance.constructor === FooComponent) {  
  (<FooComponent>componentRef.instance).data = 'hello';  
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we run the this.componentFactoryResolver.resolveComponentFactory method with the bannerItem as set by:

const bannerItem = this.banners[this.currentBannerIndex];
Enter fullscreen mode Exit fullscreen mode

Then we clear the current view by writing:

const viewContainerRef = this.bannerHost.viewContainerRef;  
viewContainerRef.clear();
Enter fullscreen mode Exit fullscreen mode

Next, we load the component by writing:

const componentRef = viewContainerRef.createComponent(componentFactory);
Enter fullscreen mode Exit fullscreen mode

Finally, we check that the component is an instance of the FooComponent . If it is, then we load the string data property as indicated in the data field of FooComponent as follows:

if (componentRef.instance.constructor === FooComponent) {  
  (<FooComponent>componentRef.instance).data = 'hello';  
}
Enter fullscreen mode Exit fullscreen mode

We used componentRef.instance.constructor to get which component instance is loaded.

Then if the component is an instance of FooComponent , we cast it to the FooComponent and set the data property of it to 'hello' as follows:

(<FooComponent>componentRef.instance).data = 'hello';
Enter fullscreen mode Exit fullscreen mode

In the end, we should see foo hello and bar displayed one after the other in a 3-second cycle.

Conclusion

Displaying multiple Angular components dynamically involves several steps.

First, we have to create a directive to inject the components that we want to load.

Then we have to add an ng-template with the directive that we just created applied to it.

Next, we clear the existing view and use the ComponentFactoryResolver to clear the screen and then load the component that we want to load.

We can check the instance of the component that’s loaded and then do different things according to what component instance is loaded.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .