Angular 2.x+ 如何动态装载组件
在 Angular 1.x 中我们可以使用 $compile 方法编译模板达到动态加载组件的目的,然而在 ng2 中则没有那么简单,下面的例子即为动态加载广告组件的过程。
首先,在添加组件前,我们需要定义一个锚点用于标识组件插入的位置,广告 banner 组件使用辅助指令 AdDirective 来标识模板中的有效插入位置。
// src/app/ad.directive.ts
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[ad-host]',
})
export class AdDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
AdDirective 通过注入 ViewContainerRef 来访问作为被插入组件宿主的节点视图容器。
class ViewContainerRef {
createComponent(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][]) : ComponentRef<C> { }
}
- Component 所对应的 ComponentFactory 即是编译后的 Component 版本,所有与 Angular 运行时的实际交互都是通过 ComponentFactory 进行的
- 如果在 ViewContainerRef 中创建多个 Component/Template 那么 index 表示当前动态组件插入的位置
- 默认 Injector 是继承的,如果需要提供额外的 Provider,则需要使用 Injector 参数声明注入器(IoC 容器)
- projectableNodes 参数表示组件的 Transclude
ng-template 就是应用 AdDirective 组件的地方,用来告诉 Angular 动态组件加载到哪里。
// src/app/ad-banner.component.ts (template)
template: `
<div class="ad-banner">
<h3>Advertisements</h3>
<ng-template ad-host></ng-template>
</div>
`
ng-template 是创建动态组件较好的选择,因为它不会渲染多余的输出。AdBannerComponent 使用 AdItem 对象的数组作为输入属性,并通过 getAds 方法周期性地遍历 AdItem 对象数组,每隔 3s 调用 loadComponent 方法动态加载组件。
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() { ... }
getAds() {
this.interval = setInterval(() => {
this.loadComponent();
}, 3000);
}
}
loadComponent 选择某个广告对象后,使用 ComponentFactoryResolver API 为每个相应的组件解析出一个 ComponentFactory 工厂类,用于创建组件的实例。
let adItem = this.ads[this.currentAddIndex];
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
这里需要注意的是,ViewContainerRef 只能通过在构造函数中直接声明来注入,因为 ViewContainerRef 的注入并不通过注入器,而是由编译器直接注入的,所以无法通过 Service 注入。
let viewContainerRef = this.adHost.viewContainerRef;
viewContainerRef.clear();
我们可以调用 viewContainerRef 的 createComponent 方法实例化组件,该方法返回被加载的动态组件的引用,我们可以通过该引用修改组件的属性或者调用其方法。
let componentRef = viewContainerRef.createComponent(componentFactory);
(<AdComponent>componentRef.instance).data = adItem.data;
同时这里存在一个容易误解的地方就是:创建动态组件必须通过 ViewContainerRef,实际上并不是,ComponentFactory 本身就具有实例化组件的能力。
class ComponentFactory {
create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any) : ComponentRef<C> { }
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。