一、引言
今天看到文章:https://segmentfault.com/a/1190000015944548 。于是专门研究一下ngTemplateOutlet用法!!!!
官方定义 :
NgTemplateOutlet: 它是结构指令,根据一个提前备好的 [TemplateRef](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fangular.cn%2Fapi%2Fcore%2FTemplateRef)
插入一个内嵌视图。 你可以通过设置 [[ngTemplateOutletContext](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fangular.cn%2Fapi%2Fcommon%2FNgTemplateOutlet%23ngTemplateOutletContext)]
来给 [EmbeddedViewRef](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fangular.cn%2Fapi%2Fcore%2FEmbeddedViewRef)
附加一个上下文对象。 [[ngTemplateOutletContext](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fangular.cn%2Fapi%2Fcommon%2FNgTemplateOutlet%23ngTemplateOutletContext)]
是一个对象,该对象的 key 可在模板中使用 let
语句进行绑定。
示例: <ng-container *ngTemplateOutlet="templateRefExp; context: contextExp">
*我测试: 需要要自定义标题或页脚的内容。 比如在一个页面上,引用nz-card时,把主页面上 如果要编写这样的组件,需要考虑几个问题, 1、如何引用主页面上的一个模板元素 ( 使用本地变量 # ,我记得以前官方文档叫“局部模版变量"或“模板引用变量”) 上下文传递很重要。组件为了使用上的灵活,一部分内容定义在组件之外的(即主页面上),当它插入到子组件中的时候,必然要显示子组件内的一些数据,它才有意义。 5、模板元素如何使用上下文? (使用 模板输入变量 定义上下文数据时, myContext = { $implicit: 'World', valueInContent: "子组件内的value" }; $implicit:是默认导出值。当let-item 后没= 号时,item引用它。 当 let- valueInContent =valueInContent 时,在模板元素内部可以插值{{valueInContent}} 完整而精巧的小例子,包含上面5个要点,要认真融会贯通呀: 源码如下: ng-zorro中,大量的组件的参数支持传入模板引用,它的使用无处不在,要研究框架是如何实现的传入模板引用的参数。 以nz-card为例,它的API说明 : 凡是支持插入外部的模板引用的参数, 它们的类型就是: string|TemplateRef 翻看ng-zorro的nz-card 源码,摘引如下,现在看它是不是超级简单: 对于TemplateRef 对于string|TemplateRef *nzStringTemplateOutlet 是ng-zorro官方扩展的指令,很实用且复杂的指令,它即接受string|TemplateRef 1、构造函数注入变量, 为什么能这样注入变量,这得参见官方文档: viewContainer注入: defalultTemplate注入: 2、动态创建EmbeddedView, 这是动态组件时,经常要使用的函数! 使用nzStringTemplateOutlet的情景如下,nzExtra的值:string | TemplateRef 1、首先获得各自的 TemplateRef 2、创建EmeddedView。 3、监听指令ngOnChanges。 5、若需要重建视图,则重建视图。 通过以上源码,nzStringTemplateOutlet指令就可以监听绑定模板变化,以及上下文数据变化,更新视图。 正由于这个指令强大,方便,我们在使用ng-zorro的组件时,才倍感方便顺手。 最后,在ng-zorro源码中,搜索 nzStringTemplateOutlet绑定上下文时的语法,引自nz-form-control源码: 突然很意外,为什么不是下面这样使用? 为什么写context就能自动赋值到nzStringTemplateOutletContext上去?? 偶然看到Common 模块中,还有NgComponentOutlet指令,根据名称也能猜出它的作用,直接把一个组件绑定过来,此时只要传递一个组件的类名即可! 我想起去年编写实时数据看板的功能时,每一个看板的子组件是根据配置,动态生成一个类的工厂函数,再创建类view,再插入到相应的containerview中去。使用目的
比如编写弹窗组件,不建议在组件模板中写死标题和页脚的内容,当在页面上使用该组件时, 页面可以动态向指定组件内占位传入“一些内容”,组件会把它们插入到它想要的地方!
比如ng-zorro中,也大量这样用法,https://ng.ant.design/components/card/zh#components-card-demo-simple二、组件自定义输入内容
2、引用的变量如何传递给子组件中 (子组件定义@Input ,使用类型为TemplateRef
3、子组件如何使用这个引用变量 ( 在模版中,用ngTemplateOutlet 绑定这个变量即可)
4、引用的模板元素从主页面上来, 如何把子组件的数据(即子组件中上下文)传递给这个引用元素上来?(向ngTemplateOutlet 传入 context: myContext”)
ngTemplateOutlet 不仅用于绑定元素,还负责把子组件中的一个数据上下文传递进去.let
的形式,接收上下文属性值,再用 {{ }}语法插入值)
参考官方文档:/// 主页面
@Component({
selector: 'app-root',
template: `
<h1>Angular's ngTemplateOutlet 完整示意-----我是主页</h1>
<app-content [dynamicRef]="usedByContent"></app-content>
<ng-template #usedByContent let-name let-valueInContent=valueInContent>
<div style="margin-left:20px;border:dashed;">
<div>Hello {{name}}! 组件内的上下文绑定: {{valueInContent}} ....</div>
<div> 主页面的变量绑定: {{valueInApp}}</div>
</div>
</ng-template>`,
})
export class AppComponent {
public valueInApp = "valueInApp :)";
}
/// 一个子组件
@Component({
selector: 'app-content',
template: `
<div>我是子组件,下面的内容是动态加载 :)</div>
<template
*ngTemplateOutlet="dynamicRef context: myContext">
</template>
`,
})
export class AppContent {
display = false;
@Input() dynamicRef: TemplateRef<HTMLDivElement>;
myContext = { $implicit: 'World', valueInContent: "子组件内的value" };
}
三、ng-zorro 的组件的实现
支持TemplateRef参数的组件的实现
// 组件TS
@Input() nzExtra: string | TemplateRef<void>;
// 组件HTML
<div class="ant-card-extra" *ngIf="nzExtra">
<ng-container *nzStringTemplateOutlet="nzExtra">{{ nzExtra }}</ng-container>
</div>
// 组件TS
@Input() nzActions: Array<TemplateRef<void>> = [];
// 组件HTML
<ul class="ant-card-actions" *ngIf="nzActions.length">
<li *ngFor="let action of nzActions">
<span><ng-template [ngTemplateOutlet]="action"></ng-template></span>
</li>
</ul>
我测试 [ngTemplateOutlet] 不可绑定上下文,但*ngTemplateOutlet就可以,所以我们尽量使用*的格式吧!*nzStringTemplateOutlet探险
现在要小心翼翼的进入源码探险之旅了!2个知识必备:
viewContainer代表指令的宿主元素,此处它承担渲染容器的作用,
defalultTemplate注入的是宿主元素的内容元素,就是上面的{{nzExtra}}部分。// 注入viewContainer 宿主元素, 注入 defaultTemplate的 TemplateRef<void>
constructor(private viewContainer: ViewContainerRef, private defaultTemplate: TemplateRef<void>) {}
this.viewContainer.createEmbeddedView( 模版引用,上下文数据 ); //返回一个EmbeddedView
分析源码逻辑
<ng-container *nzStringTemplateOutlet="nzExtra">{{ nzExtra }}</ng-container>
参数为:字符串string时, 通过注入, {{nzExtra}} ==> defalultTemplate.
参数为:模板TemplateRef时, 通过指令赋值, 把 变量nzExtra==>inputTemplate.
使用viewContainer.createEmbeddedView方法, 把TemplateRef和上下文来生成View.
4、判断是否重新创建视图。因为用户动态修改引用或上下文时,整个视图要重建!
是否重建View,就是判断nzStringTemplateOutletContext, nzStringTemplateOutlet 是否变化
a) 如果nzStringTemplateOutlet第一次赋值,或nzStringTemplateOutlet变化前后不都是string, 要重建视图
b) 如果nzStringTemplateOutletContext 的属性shape有变化时,也要重建视图。
(注:新上下文的属性比旧上下文属性个数多时才变化, 若只是属性值变化,是不用更新视图)
6、无需重建视图,则进一步判断是否是TemplateRef参数情况,如果上下文数据变化了,动态更新EmeddedView的值!绑定上下文件的语法
// ng-zorro的官方写法
<ng-container *nzStringTemplateOutlet="nzSuccessTip;context:{$implicit:validateControl};">
{{ nzSuccessTip }}
</ng-container>
可能这是由于Angular编译时,特殊的处理吧! // 这个写法编译报错,container 不存在nzStringTemplateOutletContext属性
<ng-container *nzStringTemplateOutlet="nzSuccessTip"
[nzStringTemplateOutletContext]="{$implicit:validateControl}“ >
{{ nzSuccessTip }}
</ng-container>
四、它的同类指令:NgComponentOutlet
当时以为很完美的方法,其实是绕了很多弯路,直接用NgComponentOutlet一句话不就够了吗,当时真的蠢,不过看了许多文章,也没有讲到这个指令的地方。Angular的知识点太多太碎了呀!