学习angular吃力篇之一------动态组件
前言
最近公司项目要求用angular+typescript,对于在这两方面零基础的我也只好硬着头皮学呀!都知道angular在国内不太火,所以参考的资料也很少,学习的时候经常遇到一些问题。今天就总结一下让我不太能理解的动态组件,也方便自己以后查阅和复习。文章中如有不足之处,希望大家多多指出。
我理解的动态组件
一个页面中某一区域的内容不是固定不变的。有时候需要根据我们的一些条件,动态的展示里面的内容。这时候我们可能会写几个子组件,当在不同的条件显示相应的子组件内容。
实现
理一下思路
- 我们需要定义一个锚点,将我们的组件插入到固定位置。
- 将我们准备的组件解析成具体的组件实例
- 需要将这个组件添加到之前的“锚点”模板中
看看angular中如何实现
前提知识(这里有好多个xxxxxref)
-
ViewContainerRef 官方文档
作用:可以将一个或者多个视图附着到组件中的容器中。(创建视图和管理视图)
-
ApplicationRef 官方文档
作用:包含对根视图的引用。他的tick()方法来全局性调用变化检测。他的attachView()方法将视图包含到变化检测中,他的detachView()方法将视图移除变化检测。
-
ComponentRef 官方文档
作用:表示由componentFactory创建的组件。提供对组件实例和相关对象的访问。并提供销毁实例的方法。
-
TemplateRef 官方文档
作用:表示一个内嵌模板。它可用于实例化的内嵌视图。要想根据模板实例化内嵌的视图,请使用 ViewContainerRef 的 createEmbeddedView() 方法。
-
ElementRef 官方文档
作用:对视图中某个原生属性的包装器。ElementRef 的背后是一个可渲染的具体元素。在浏览器中,它通常是一个 DOM 元素。
-
ViewRef 官方文档
作用:表示一个angular视图,特别是由组件定义的宿主视图。
看看angular中的动态组件实现方式
准备一个指令
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[ad-host]',
})
export class AdDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
[ad-host]指令插入到某个模板上就表示动态组件的内容将会插入到该模板中
创建一系列动态组件
这是一个创建一系列动态组件的类
import { Type } from '@angular/core';
export class AdItem {
constructor(public component: Type<any>, public data: any) {}
}
用上面的类构造出动态组件实例
getAds() {
return [
new AdItem(HeroProfileComponent, {name: 'Bombasto', bio: 'Brave as they come'}),
new AdItem(HeroProfileComponent, {name: 'Dr IQ', bio: 'Smart as they come'}),
new AdItem(HeroJobAdComponent, {headline: 'Hiring for several positions',
body: 'Submit your resume today!'}),
new AdItem(HeroJobAdComponent, {headline: 'Openings in all departments',
body: 'Apply today'}),
];
}
简单的注册表
上面步骤创建好了组件实例之后并不能直接用来使用。 需要用componentFactoryResolver方法提供对该组件实例和相关对象的访问。
loadComponent() {
this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
const adItem = this.ads[this.currentAdIndex];
const viewContainerRef = this.adHost.viewContainerRef;
viewContainerRef.clear();
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
const componentRef = viewContainerRef.createComponent(componentFactory);
(<AdComponent>componentRef.instance).data = adItem.data;
}
- 通过viewContainerRef获取到组件视图容器,将里面的内容先清空.
- 它使用 ComponentFactoryResolver 来为每个具体的组件解析出一个 ComponentFactory。 然后 ComponentFactory 会为每一个组件创建一个实例。
- 调用 ViewContainerRef 的 createComponent()。实例化一个 Component 并把它的宿主视图插入到本容器的指定 index 处。
我以为我懂了,但是看了同事的代码之后......
- 同事的代码如下
loadComponent(name: nameForSele, metaData: IObj<any>): IDynamicRes {
let dyId = this.setProId();//前端生成动态组件id
Object.assign(metaData, { dyId })
let selector = SELECTORDATA[name];//选择显示的动态组件
let target = ComPoolService.getCom(selector);
let compFac = this.componentFactoryResolver.resolveComponentFactory(target);//来为每个具体的组件解析出一个 ComponentFactory。 然后 ComponentFactory 会为每一个组件创建一个实例。
let propArr = ComPoolService.getProp(selector);
let compRef = compFac.create(this.injector);
propArr.forEach(item => {
compRef.instance[item.key] = metaData[item.key];
})
this.applicationRef.attachView(compRef.hostView);
return {
ele: (compRef.hostView as EmbeddedViewRef<any>).rootNodes[0],
destory: () => {
this.applicationRef.detachView(compRef.hostView);
compRef.destroy();
},
type: name,
dyId
}
}
他的思路其实是利用applicationRef,创建element元素。利用attachView()包含到变化检测中。组件销毁的时候利用detachView()将视图移除变化检测
open(name: nameForSele, metaData: IObj<any>): string {
let { ele, destory, type, dyId } = this.loadComponent(name, metaData);
debugger
document.body.appendChild(ele);
let obj: any = {
dyId,
destory,
type
}
switch (name) {
case 'contentMenu':
Object.assign(obj, { active: true })
break;
case 'loadCom':
break
default:
}
this.dynamicData.push(obj);
return dyId
}
不利用viewContainerRef,利用applicationRef,动态插入一个组件到指定的DOM节点中
addDynamicComponent() {
let factory = this.resolver.resolveComponentFactory(SimpleComponent);
let newNode = document.createElement('div');
newNode.id = 'placeholder';
document.getElementById('container').appendChild(newNode);
const ref = factory.create(this.injector, [], newNode);
this.app.attachView(ref.hostView);
}