学习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); }