这篇文章主要介绍使用Angular api 和 CDK Portals两种方式实现动态创建组件,另外还会讲一些跟它相关的知识点,如:Angular多级依赖注入、ViewContainerRef,Portals可以翻译为 门户 ,我觉得放到这里叫 入口 更好,可以理解为动态创建组件的入口,类似于小程序或者Vue中的Slot.
cdk全名Component Development Kit 组件开发包,是Angular官方在开发基于Material Design的组件库时抽象出来单独的一个开发包,里面封装了一些开发组件时的公共逻辑并且跟Material Design 设计无关,可以用来封装自己的组件库或者直接在业务开发中使用,里面代码抽象程度非常高,非常值得学习,现在我用到的有Portals、Overlay(打开浮层相关)、SelectionModel、Drag and Drop等.
官方:
中文翻译:
动态创建组件
想想应用的路由,一般配置路由地址的时候都会给这个地址配置一个入口组件,当匹配到这个路由地址的时候就在指定的地方渲染这个组件,动态创建组件类似,在最页面未接收到用户行为的时候,我不知道页面中这块区域应该渲染那个组件,当页面加载时根据数据库设置或者用户的操作行为才能确定最终要渲染的组件,这时候就要用代码动态创建组件把目标组件渲染到正确的地方。
示例截图
使用Angular API动态创建组件
该路由的入口组件是PortalsEntryConponent组件,如上面截图所示右侧有一块虚线边框的区域,里面具体的渲染组件不确定。
第一步
先在视图模板中定义一个占位的区域,动态组件就要渲染在这个位置,起一个名称#virtualContainer
文件portals-entry.component.html
?1234[/code>div class="portals-outlet"
第二步
通过ViewChild取到这个container对应的逻辑容器
文件portals-entry.component.ts
?12@ViewChild('virtualContainer', { read: ViewContainerRef }) virtualContainer: ViewContainerRef;
第三步
处理单击事件,单击按钮时动态创建一个组件,portals-entry.component.ts完整逻辑
?12345678910111213141516171819202122232425262728293031import { TaskDetailComponent } from '../task/task-detail/task-detail.component';@Component({ selector: 'app-portals-entry', templateUrl: './portals-entry.component.html', styleUrls: 【'./portals-entry.component.scss'】, providers: 【 】})export class PortalsEntryComponent implements OnInit { @ViewChild('virtualContainer', { read: ViewContainerRef }) virtualContainer: ViewContainerRef; constructor( private dynamicComponentService: DynamicComponentService, private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector, ) { } ngOnInit() { } openTask() { const task = new TaskEntity(); task.id = '1000'; task.name = '写一篇关于Portals的文章'; const componentFactory = this.componentFactoryResolver.resolveComponentFactory(TaskDetailComponent); const componentRef = this.virtualContainer.createComponent/code>TaskDetailComponent
代码说明
openTask()方法绑定到模板中按钮的单击事件
导入要动态创建的组件TaskDetailComponent
constructor注入injector、componentFactoryResolver 动态创建组件需要的对象,只有在组件上下文中才可以拿到这些实例对象
使用api创建组件,现根据组件类型创建一个ComponentFactory对象,然后调用viewContainer的createComponent创建组件
使用componentRef.instance获取创建的组件实例,这里用来设置组件的task属性值
其它
ViewContainerRef除了createComponent方法外还有一个createEmbeddedView方法,用于创建模板
?123@ViewChild('customTemplate')customTemplate: TemplateRef;this.virtualContainer.createEmbeddedView(this.customTemplate, { name: 'pubuzhixing' });
createEmbeddedView方法的第二个参数,用于指定模板的上下文参数,看下模板定义及如何使用参数
?123
自定义模板,传入参数name:{{name}}
此外还可以通过ngTemplateOutlet直接插入内嵌视图模板,通过ngTemplateOutletContext指定模板的上下文参数
?1
小结
分析下Angular动态创建组件/内嵌视图的API,动态创建组件首先需要一个被创建的组件定义或模板声明,另外需要Angular上下文的环境来提供这个组件渲染在那里以及这个组件的依赖从那获取,viewContainerRef是动态组件的插入位置并且提供组件的逻辑范围,此外还需要单独传入依赖注入器injector,示例直接使用逻辑容器的injector,是不是很好理解。
示例仓储:
CDK Portal 官方文档介绍
这里先对Portal相关的内容做一个简单的说明,后面会有两个使用示例,本来这块内容准备放到最后的,最终还是决定放在前面,可以先对Portals有一个简单的了解,如果其中有翻译不准确请见谅。
地址:
-------- 文档开始
portals 提供渲染动态内容到应用的可伸缩的实现,其实就是封装了Angular动态创建组件的过程
Portals
这个Portal指是能动态渲染一个指定位置的 UI块 到页面中的一个 open slot 。
UI块 指需要被动态渲染的内容,可以是一个组件或者是一个模板,而 open slot 是一个叫做PortalOutlet的开放的占位区域。
Portals和PortalOutlets是其它概念中的低级的构造块,像overlays就是在它基础上构建的
?1Portal 包括动态组件的抽象类,可以是TemplatePortal(模板)或者ComponentPortal(组件)
方法描述
attach(PortalOutlet): T
把当前Portal附加到宿主上
detach(): void
把Portal从宿主上拆离
isAttached://代码效果参考:http://www.jhylw.com.cn/160723361.html
boolean
当前Portal是否已经附加到宿主上
?1PortalOutlet 动态组件的宿主
方法描述
attach(Portal): any
附加指定Portal
detach(): any
拆离当前附加Portal
dispose(): void
永久释放宿主资源
hasAttached: boolean
当前是否已经装在Portal
代码片段说明
CdkPortal
?12345678
The content of this template is captured by the portal.
可以通过ViewChild、ViewChildren获取到该Portal,类型应该是CdkPortal,如下所示:
?12// 模板中的Portal@ViewChild(CdkPortal) templateCDKPortal: TemplatePortal;
ComponentPortal
组件类型的Portal,需要当前组件在NgModule的entryComponents中配置才能动态创建该组件。
?1this.userSettingsPortal = new ComponentPortal(UserSettingsComponent);
CdkPortalOutlet
使用指令可以把portal outlet添加到一个ng-template,cdkPortalOutlet把当前元素指定为PortalOutlet,下面代码把userSettingsPortal绑到此portal-outlet上
?12
cdkPortalOutlet】="userSettingsPortal"
----- 文档完毕
Portals使用示例
这里首先使用新的api完成和最上面示例一样的需求,在同样的位置动态渲染TaskDetailComponent组件。
第一步
同样是设置一个宿主元素用于渲染动态组件,可以使用指令cdkPortalOutlet挂载一个PortalOutlet在这个ng-container元素上
?1234[/code>div class="portals-outlet"
第二步
与 使用Angular API动态创建组件 一节使用同一个逻辑元素作为宿主,只不过这里的获取容器的类型是CdkPortalOutlet,代码如下
?12@ViewChild('virtualContainer', { read: CdkPortalOutlet })virtualPotalOutlet: CdkPortalOutlet;
第三步
创建一个ComponentPortal类型的Portal,并且将它附加上面获取的宿主virtualPotalOutlet上,代码如下
?12345678portalOpenTask() { this.virtualPotalOutlet.detach(); const taskDetailCompoentPortal = new ComponentPortal ; const ref = this.virtualPotalOutlet.attach(taskDetailCompoentPortal); // 此处同样可以 通过ref.instance传递task参数}
小结
这里是使用ComponentPortal的示例实现动态创建组件,Portal还有一个子类TemplatePortal是针对模板实现的,上节 CDK Portal 官方文档介绍 中有介绍,这里就不在赘述了。总之使用Portals可以很大程度上简化代码逻辑。
示例仓储:
Portals 源码分析
上面只是使用Portal的最简单用法,下面讨论下它的