目的:使用 ComponentFactoryResolver 来动态添加组件。
案例需求:
设计一个滚动广告牌的实现,广告牌可以滚动播放不同的广告。
思路:
用只支持静态组件结构的模板实现是不显示的,需要一种新的组件加载方式,它不需要在广告条组件的模板中引用固定的组件。
1.指令:
背景:在添加组件之前,先要定义一个锚点来告诉Angular要把组件插入到什么地方。
解决办法:广告条使用一个名叫AdDirective的辅助指令来在模板中标记出有效的插入点。
AdDirective指令注入了ViewContainerRef来获取对容器视图的访问权,这个容器即动态显示播放的组件的宿主。
选择器名称:ad-host, 这个指令将会被应用到元素上。
src/app/ad.directive.ts
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[adHost]',
})
export class AdDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
2.加载组件
ad-banner.component.ts:广告条的实现代码
<ng-template>元素:应用指令的地方。(这样,angular知道要把组件动态加载到哪里)
src/app/ad-banner.component.ts (template)
template: `
<div class="ad-banner-example">
<h3>Advertisements</h3>
<ng-template adHost></ng-template>
</div>
`
3.广告条组件的实现
AdItem(要加载到广告条的组件类, 包括了要绑定的数据)
AdBannerComponent(广告条组件类)接收一个AdItem对象的数组作为输入。通过getAds()方法,AdBannerComponent可以循环遍历AdItem的数组,并且每3秒调用一次loadComponent()来加载数组。
注:
1)loadComponent()如何选择广告。规则:循环选取。将当前currentAdIndex递增一,用它除以AdItem数组长度的余数作为新的currentAdIndex的值,最后用这个值来从数组中选取一个adItem。
2)怎么加载组件实例:
在loadComponent()选取了一个广告之后,它使用ComponentFactoryResolver来为每个具体的组件解析出一个ComponentFactory。然后ComponentFactory会为每一个组件创建一个实例。
需要把viewContainerRef指向这个实例。
viewContainerRef --》adHost --指明了位置;
调用viewContainerRef的createComponent(),将返回的引用指向要被加入的组件实例;
AdDirective
曾在它的构造函数中注入了一个 ViewContainerRef
。 因此这个指令可以访问到这个你打算用作动态组件宿主的元素。
4. 实现
1)\my-app\src\app\ad-banner.component.ts
import { Component, Input, OnInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core';
import { AdDirective } from './ad.directive';
import { AdItem } from './ad-item';
import { AdComponent } from './ad.component';
@Component({
selector: 'app-ad-banner',
template: `
<div class = "ad-banner-example">
<h3>Advertisement</h3>
<ng-template adHost></ng-template>
</div>
`
})
export class AdBannerComponent implements OnInit, OnDestroy {
@Input() ads: AdItem[];
currentAdIndex = -1;
@ViewChild(AdDirective, {static: true}) adHost: AdDirective;
interval: any;
constructor(private componentFactoryResolver: ComponentFactoryResolver){}
ngOnInit(){
this.loadComponent();
this.getAds();
}
ngOnDestroy(){
clearInterval(this.interval);
}
loadComponent(){
this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
const adItem = this.ads[this.currentAdIndex];
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
const viewContainerRef = this.adHost.viewContaineRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent<AdComponent>(componentFactory);
componentRef.instance.data = adItem.data;
}
getAds() {
this.interval = setInterval( () => {
this.loadComponent();
}, 3000);
}
}
2)my-app\src\app\ad-item.ts
import { Type } from '@angular/core';
export class AdItem {
constructor(public component: Type<any>, public data: any){}
}
3)my-app\src\app\ad.component.ts
export interface AdComponent {
data: any;
}
4)my-app\src\app\ad.directive.ts
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[adHost]'
})
export class AdDirective {
constructor(public viewContaineRef: ViewContainerRef){}
}
5)my-app\src\app\ad.service.ts
import { Injectable } from '@angular/core';
import { HeroJobAdComponent } from './hero-job-ad.component';
import { HeroProfileComponent } from './hero-profile.component';
import { AdItem } from './ad-item';
@Injectable()
export class AdService {
getAds(){
return [
new AdItem(HeroProfileComponent, { name: 'BomBasto',
bio: 'Brave as they come'}),
new AdItem(HeroProfileComponent, { name: 'Dr IO',
bio: 'Smart as they come'}),
new AdItem(HeroJobAdComponent, {headline: 'Hiring for several positions',
body: 'Apply today'})
];
}
}
6)my-app\src\app\app.component.ts
import { Component, OnInit } from '@angular/core';
import { AdService } from './ad.service';
import { AdItem } from './ad-item';
@Component({
selector: 'app-root',
template: `
<div>
<app-ad-banner [ads]="ads"></app-ad-banner>
</div>
`
})
export class AppComponent implements OnInit {
ads: AdItem[];
constructor(private adService: AdService) {}
ngOnInit() {
this.ads = this.adService.getAds();
}
}
7)my-app\src\app\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 { AdService } from './ad.service';
import { AdBannerComponent } from './ad-banner.component';
import { HeroJobAdComponent } from './hero-job-ad.component';
import { HeroProfileComponent } from './hero-profile.component';
import { AdDirective } from './ad.directive';
@NgModule({
declarations: [
AppComponent,
AdBannerComponent,
HeroJobAdComponent,
HeroProfileComponent,
AdDirective
],
imports: [
BrowserModule,
AppRoutingModule
],
entryComponents: [HeroJobAdComponent, HeroProfileComponent],
providers: [AdService],
bootstrap: [AppComponent]
})
export class AppModule {
constructor() {}
}
8)my-app\src\app\hero-job-ad.component.ts
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;
}
9)my-app\src\app\hero-profile.component.ts
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;
}