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
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 { }
banner-host.directive.ts
:
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[banner-host]'
})
export class BannerHostDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
bar.component.html
:
bar
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() {
}
}
foo.component.html
:
foo {{data}}
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);
}
}
app.component.html
:
<ng-template banner-host></ng-template>
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';
}
In the code above, we run the this.componentFactoryResolver.resolveComponentFactory
method with the bannerItem
as set by:
const bannerItem = this.banners[this.currentBannerIndex];
Then we clear the current view by writing:
const viewContainerRef = this.bannerHost.viewContainerRef;
viewContainerRef.clear();
Next, we load the component by writing:
const componentRef = viewContainerRef.createComponent(componentFactory);
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';
}
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';
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.