[no_toc]In the tutorial, ozenero.com shows how to dynamically load an Angular components by examples. What we introduce:
- Create Anchor Directive
- Dynamically Loading and Resolving Components
Related posts:
– Angular 6 Component – How to create & integrate New Angular 6 Component
– Angular 6 Routing/Navigation – with Angular Router Service
Overview
An Agnular application may need to load new components at runtime, so component’s templates are not always fixed.
The following examples shows how to build an dynamic news banner.
New news components are added frequently so it is impractical to use a template with a static component structure.
Angular Anchor Directive
We need define an anchor point to tell Angular where to insert components for adding a new components:
Now we build an NewsDirective
to mark valid insertion points in the template:
import { Directive, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[news-host]' }) export class NewsDirective { constructor(public viewContainerRef: ViewContainerRef) { } }
– NewsDirective
injects ViewContainerRef
to access to the view container of the element that will host the dynamically added component.
news-host
is used to apply the directive to the element.
Angular Dynamic Loading Components
We implement an NewsBannerComponent
in news-banner.component.ts
file.
... @Component({ selector: 'app-news-banner', template: `` }) export class NewsBannerComponent implements OnInit, OnDestroy { ...Dynamic News Banner - Copyright by http://ozenero.com
The
The
Angular Resolving Components
NewsBannerComponent
takes an array of NewsItem
objects as input that comes NewsService
.
NewsItem
object contains component object to load and any data to bind to the component.
import { Type } from '@angular/core'; export class NewsItem { constructor(public component: Type, public data: any) { } }
NewsService
returns the actual news content for banner:
import { Injectable } from '@angular/core'; import { SportNewsComponent } from './sport-news/sport-news.component'; import { NewsItem } from './news-item'; import { BusinessNewsComponent } from './business-news/business-news.component'; @Injectable({ providedIn: 'root' }) export class NewsService { getNews() { return [ new NewsItem(SportNewsComponent, { title: "2019 Women's World Cup: Spain's Women come back to beat South Africa", from: "euronews.com" }), new NewsItem(BusinessNewsComponent, { title: 'Walmart will save $30 million by putting new step stools in its warehouses', author: 'Lauren Thomas' }), new NewsItem(SportNewsComponent, { title: "Women's World Cup: Germany Women 1-0 China Women", from: 'bbc.com' }), new NewsItem(BusinessNewsComponent, { title: 'Elliott Management to acquire Barnes & Noble for $683 million', author: 'Lauren Hirsch' }) ]; } }
NewsBannerComponent
loop through the array of NewsItems
and uses loadComponent()
to load a new component every 3 seconds.
export class NewsBannerComponent implements OnInit, OnDestroy { @Input() news: NewsItem[]; currentNewsIndex = -1; @ViewChild(NewsDirective, { static: true }) newsHost: NewsDirective; interval: any; constructor(private componentFactoryResolver: ComponentFactoryResolver) { } ngOnInit() { this.loadComponent(); this.getNews(); } ngOnDestroy(): void { clearInterval(this.interval); } loadComponent() { this.currentNewsIndex = (this.currentNewsIndex + 1) % this.news.length; let newsItem = this.news[this.currentNewsIndex]; let componentFactory = this.componentFactoryResolver.resolveComponentFactory(newsItem.component); let viewContainerRef = this.newsHost.viewContainerRef; viewContainerRef.clear(); let componentRef = viewContainerRef.createComponent(componentFactory); (componentRef.instance).data = newsItem.data; } getNews() { this.interval = setInterval(() => { this.loadComponent(); }, 3000); } }
– NewsBannerComponent
uses ComponentFactoryResolver
to resolve a ComponentFactory
for each specific component.
– createComponent()
method returns a reference to the loaded component. Use that reference to interact with the component by assigning to its properties or calling its methods.
Note: to ensure that the compiler generates a ComponentFactory
for dynamically loaded components, we need add dynamically loaded components to the NgModule’s entryComponents array:
entryComponents: [SportNewsComponent, BusinessNewsComponent],
NewsComponent Interface
To standardize the API for passing data to the components, we create an interface component: NewsComponent
.
export interface NewsComponent { data: any; }
Then using it in all sub-news components:
– SportNewsComponent:
import { Component, OnInit, Input } from '@angular/core'; import { NewsComponent } from '../news.component'; @Component({ template: `` }) export class SportNewsComponent implements NewsComponent { @Input() data: any; }Sport News
{{data.title}}
from: {{data.from}}
– BusinessNewsComponent:
import { Component, OnInit, Input } from '@angular/core'; import { NewsComponent } from '../news.component'; @Component({ template: `` }) export class BusinessNewsComponent implements NewsComponent { @Input() data: any; }Business News
{{data.title}}
Author: {{data.author}}
Bootstrap App Component
We edit a bootstrap AppComponent
that shows app-news-banner
component. And it also injects NewsService
to provide NewsItem
data for NewsBannerComponent
:
import { Component } from '@angular/core'; import { NewsItem } from './news-item'; import { NewsService } from './news.service'; @Component({ selector: 'app-root', template: `` }) export class AppComponent { news: NewsItem[]; constructor(private newsService: NewsService) { } ngOnInit() { this.news = this.newsService.getNews(); } }
Sourcecode
– Project structure
– Source Code:
Angular-Dynamic-Loading-Components