Component templates are not always fixed. An application may need to load new components at runtime.


This cookbook shows you how to use ComponentFactoryResolver to add components dynamically.




See the of the code in this cookbook.


Dynamic component loading


The following example shows how to build a dynamic ad banner.


The hero agency is planning an ad campaign with several different ads cycling through the banner. New ad components are added frequently by several different teams. This makes it impractical to use a template with a static component structure.

英雄管理局正在计划一个广告活动,要在广告条中显示一系列不同的广告。几个不同的小组可能会频繁加入新的广告组件。 再用只支持静态组件结构的模板显然是不现实的。

Instead, you need a way to load a new component without a fixed reference to the component in the ad banner's template.


Angular comes with its own API for loading components dynamically.

Angular 自带的API就能支持动态加载组件。

The directive


Before you can add components you have to define an anchor point to tell Angular where to insert components.


The ad banner uses a helper directive called AdDirective to mark valid insertion points in the template.



import { Directive, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[ad-host]', }) export class AdDirective { constructor(public viewContainerRef: ViewContainerRef) { } }

AdDirective injects ViewContainerRef to gain access to the view container of the element that will host the dynamically added component.


In the @Directive decorator, notice the selector name, ad-host; that's what you use to apply the directive to the element. The next section shows you how.


Loading components


Most of the ad banner implementation is in ad-banner.component.ts. To keep things simple in this example, the HTML is in the @Component decorator's template property as a template string.

广告条的大部分实现代码都在ad-banner.component.ts中。 为了让这个例子简单点,我们把HTML直接放在了@Component装饰器的template属性中。

The <ng-template> element is where you apply the directive you just made. To apply the AdDirective, recall the selector from ad.directive.ts, ad-host. Apply that to <ng-template> without the square brackets. Now Angular knows where to dynamically load components.

<ng-template>元素就是刚才制作的指令将应用到的地方。 要应用AdDirective,回忆一下来自ad.directive.ts的选择器ad-host。把它应用到<ng-template>(不用带方括号)。 这下,Angular就知道该把组件动态加载到哪里了。

src/app/ad-banner.component.ts (template)

template: ` <div class="ad-banner"> <h3>Advertisements</h3> <ng-template ad-host></ng-template> </div> `

The <ng-template> element is a good choice for dynamic components because it doesn't render any additional output.


Resolving components


Take a closer look at the methods in ad-banner.component.ts.


AdBannerComponent takes an array of AdItem objects as input, which ultimately comes from AdService. AdItem objects specify the type of component to load and any data to bind to the component.AdService returns the actual ads making up the ad campaign.

AdBannerComponent接收一个AdItem对象的数组作为输入,它最终来自AdServiceAdItem对象指定要加载的组件类,以及绑定到该组件上的任意数据。 AdService可以返回广告活动中的那些广告。

Passing an array of components to AdBannerComponent allows for a dynamic list of ads without static elements in the template.


With its getAds() method, AdBannerComponent cycles through the array of AdItems and loads a new component every 3 seconds by calling loadComponent().


src/app/ad-banner.component.ts (excerpt)

export class AdBannerComponent implements AfterViewInit, OnDestroy { @Input() ads: AdItem[]; currentAddIndex: number = -1; @ViewChild(AdDirective) adHost: AdDirective; subscription: any; interval: any; constructor(private _componentFactoryResolver: ComponentFactoryResolver) { } ngAfterViewInit() { this.loadComponent(); this.getAds(); } ngOnDestroy() { clearInterval(this.interval); } loadComponent() { this.currentAddIndex = (this.currentAddIndex + 1) % this.ads.length; let adItem = this.ads[this.currentAddIndex]; let componentFactory = this._componentFactoryResolver.resolveComponentFactory(adItem.component); let viewContainerRef = this.adHost.viewContainerRef; viewContainerRef.clear(); let componentRef = viewContainerRef.createComponent(componentFactory); (<AdComponent>componentRef.instance).data = adItem.data; } getAds() { this.interval = setInterval(() => { this.loadComponent(); }, 3000); } }

The loadComponent() method is doing a lot of the heavy lifting here. Take it step by step. First, it picks an ad.

这里的loadComponent()方法很重要。 我们来一步步看看。首先,它选取了一个广告。

How loadComponent() chooses an ad


The loadComponent() method chooses an ad using some math.


First, it sets the currentAddIndex by taking whatever it currently is plus one, dividing that by the length of the AdItem array, and using the remainder as the new currentAddIndex value. Then, it uses that value to select an adItem from the array.

(译注:循环选取算法)首先,它把currentAddIndex递增一,然后用它除以AdItem数组长度的余数作为新的currentAddIndex的值, 最后用这个值来从数组中选取一个adItem

After loadComponent() selects an ad, it uses ComponentFactoryResolver to resolve a ComponentFactory for each specific component. The ComponentFactory then creates an instance of each component.

loadComponent()选取了一个广告之后,它使用ComponentFactoryResolver来为每个具体的组件解析出一个ComponentFactory。 然后ComponentFactory会为每一个组件创建一个实例。

Next, you're targeting the viewContainerRef that exists on this specific instance of the component. How do you know it's this specific instance? Because it's referring to adHost and adHost is the directive you set up earlier to tell Angular where to insert dynamic components.

接下来,我们要把viewContainerRef指向这个组件的现有实例。但我们怎么才能找到这个实例呢? 很简单,因为它指向了adHost,而这个adHost就是我们以前设置过的指令,用来告诉Angular该把动态组件插入到什么位置。

As you may recall, AdDirective injects ViewContainerRef into its constructor. This is how the directive accesses the element that you want to use to host the dynamic component.

回忆一下,AdDirective曾在它的构造函数中注入了一个ViewContainerRef。 因此这个指令可以访问到这个被我们用作动态组件宿主的元素。

To add the component to the template, you call createComponent() on ViewContainerRef.


The 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.

createComponent()方法返回一个引用,指向这个刚刚加载的组件。 使用这个引用就可以与该组件进行交互,比如设置它的属性或调用它的方法。

Selector references


Generally, the Angular compiler generates a ComponentFactory for any component referenced in a template. However, there are no selector references in the templates for dynamically loaded components since they load at runtime.

通常,Angular编译器会为模板中所引用的每个组件都生成一个ComponentFactory类。 但是,对于动态加载的组件,模板中不会出现对它们的选择器的引用。

To ensure that the compiler still generates a factory, add dynamically loaded components to the NgModule's entryComponents array:


src/app/app.module.ts (entry components)

entryComponents: [ HeroJobAdComponent, HeroProfileComponent ],

A common AdComponent interface


In the ad banner, all components implement a common AdComponent interface to standardize the API for passing data to the components.


Here are two sample components and the AdComponent interface for reference:


import { Component, Input } from '@angular/core'; import { AdComponent } from './ad.component'; @Component({ template: ` <div class="job-ad"> <h4>{{data.headline}}</h4> {{data.body}} </div> ` }) export class HeroJobAdComponent implements AdComponent { @Input() data: any; } import { Component, Input } from '@angular/core'; import { AdComponent } from './ad.component'; @Component({ template: ` <div class="hero-profile"> <h3>Featured Hero Profile</h3> <h4>{{data.name}}</h4> <p>{{data.bio}}</p> <strong>Hire this hero today!</strong> </div> ` }) export class HeroProfileComponent implements AdComponent { @Input() data: any; } export interface AdComponent { data: any; }

Final ad banner


The final ad banner looks like this:



See the .