写在最前,本文提到的“模板”都是ng-template;假设的模态组件也只是实现模态内容;为了缩减文章的篇幅,只保留了重要的部分。完整的例子在 线上。
在开发过程中,难免会遇到公共组件需要Input模板或input组件的时候,以增加公共组件交互或展示的灵活性。
题外话:input组件的方式,可以扩展为依靠服务在业务模块中进行配置,以达到每个模块所用的同一个公共组件拥有不同的交互。下一篇文章将会谈谈。
1. 使用 ngTemplateOutlet 和 ngComponentOutlet
ngTemplateOutlet: 根据一个提前备好的 TemplateRef 插入一个内嵌视图。ngComponentOutlet: Instantiates a single Component type and inserts its Host View into current View. NgComponentOutlet provides a declarative approach for dynamic component creation.
假设要写一个表格组件,名为wtable,需要自定义单元格内容,组件ts有如下内容。
线上代码:
-
使用场景
"['姓名','年龄']" [rows]="[{name: 'ww', age: 11}, {name:'yy', age: 22}]" [cellContent]="custom">
#custom let-data let-column="column" let-row="row" let-index="index">
{{column}}: {{data}}
{{index}} 行
行数据: {{row | json}}
复制代码
-
wtable组件的html
"let row of rows;index as i;">
"let cell of row | keyvalue">
"tpl">
"tpl; context:outCellContext(cell.key, cell.value, row, i);">
"comp">
"comp">
复制代码
-
wtable组件ts
// 此处为内部变量传递给外部模板使用,所需的数据
outCellContext(cellKey, cellValue, row, index) {
return {
$implicit: cellValue, // 默认传出cellValue数据
column: cellKey, // 指定字段
row: row, // 行数据
index: index // 行索引
}
}
}复制代码
2. 使用ViewContainerRef
表示可以将一个或多个视图附着到组件中的容器。
假设要写一个模态框组件wmodal,需要将模板或组件在模态框里展示。但是又不想html里用*ngIf来判断内容的显示,两个三个还可以接受,那要是7、8个或以上呢?让我们看看下面这个例子。
线上代码。
-
使用场景
"comp" [compParams]="{data: '我是调用wmodal传入的值'}" (compOut)="wmodalOut($event)">
"modalTpl">
#modalTpl let-data let-other="other">
data: {{data | json}}
other: {{other}}
复制代码
-
在wmodal内需要做点什么呢?首先是html
// 占个位,这里我要放传入的模板或组件了
#container>
复制代码
-
接着是wmodal的ts
ngAfterContentInit() {
// 依然是判断content类型
if (this.content instanceof Type) {
const comp = this.container.createComponent(this._compFac.resolveComponentFactory(this.content));
// 将组件所需参数合并到组件实例中
if (this.compParams) {
Object.assign(comp.instance, this.compParams);
}
// 订阅组件
for (const prop in comp.instance) {
if (comp.instance.hasOwnProperty(prop)) {
const subject = comp.instance[prop];
// 筛选组件output事件
if (subject instanceof EventEmitter) {
this._compSubs.push(
// 订阅组件output事件
subject.subscribe(data => {
this.compOut.emit(data);
})
);
}
}
}
} else {
// 创建模板就比较简单了
// 留意一下第二个参数,若是需要将组建的某些数据传出则可以这样
const _data = {a: 1, b: 2};
this.container.createEmbeddedView(this.content, {$implicit: _data, other: 2});
}
}复制代码
3. 使用ApplicationRef
A reference to an Angular application running on a page.
假设还是modal组件
-
使用场景
"comp" [compParams]="{data: '我是调用wmodal2传入的值'}" (compOut)="wmodalOut($event)"> 复制代码
-
惯例,wmodal2 html
复制代码
-
wmodal2 ts
ngAfterContentInit() {
const comp = this._compFaRes.resolveComponentFactory(this.content).create(this._injector);
this._e.nativeElement.appendChild(comp.location.nativeElement);
// 此处与第2点一样,订阅output和给input赋值
// 去掉timeout你就知道为什么了
// 具体原因可以看我专栏的的第一篇文章
const timeId = setTimeout(() => {
this._appref.attachView(comp.hostView);
clearTimeout(timeId);
}, 100)
}复制代码
结束语
本文并未对3种方式进行对比,只是个人对于3种方式的使用理解。 简单的总结一下:
-
使用1,会让html代码比较多,不易维护;而且1是通过2来实现得, 传送门;
-
1、2都需要插座代码(可见的outlet),对于模板的创建都需要context来传递变量,3不需要插座代码;
-
2是比较常见的使用方式
-
使用3的方式来仅可以创建组件且还有更大的用处
-
创建组件都需要手动为input赋值并订阅output
有兴趣的还可以看看angular源码,其中 ngFor和 ngIf皆是由第2种方式来实现得。
参考资料:
-
angular.cn
-
深入Angular:理解Component动态加载
-
Angular 4.x 修仙之路: Angular 2 TemplateRef & ViewContainerRef