我们可能已经在日常的开发中使用了ng-template
的核心指令,比如ng-if/esle
或者ngSwitch
。ng-template
指令和相关的ngTemplateOutlet
指令是非常强大的Angular功能,支持各种高级用法。再搭配ng-container
组合使用会非常方便和惊艳。
ng-template指令
就像名字一样,ng-template
指令代表了一个Angular模板,它表示标签里面的内容是包含于一个模板,然后可以将其与其他模板组合,以形成最终的组件模板。
很多微语法的结构型指令在背后都是在使用ng-template
,比如:*ngIf
、*ngFor
、*ngSwitch
等。
比如,我们在展示页面的时候会有接口的请求,当接口请求中的时候页面展示loading,请求完毕后展示具体内容,我们可以先声明一个loading的模板:
1 | <ng-template #loadingTemp> |
然后结合*ngIf
来进行控制:
1 | <div class="album-component" *ngIf="!loading else loadingTemp"> |
这是一种普遍的用法,微语法*ngIf
的else
子句指向一个模板,模板的名称为loadingTemp
。这个名称是通过在模板上面声明模板引用变量产生的。
这个微语法其实分解开来是这样子的:
1 | <ng-template [ngIf]="!loading" [ngIfElse]="loadingTemp"> |
微语法给我们提供了很大的便利,我们来分析下*ngIf
指令期间的变化:
- 使用了
*ngIf
指令的元素会被移动到ng-template
标签里面。 *ngIf
指令的表达式被拆分为ngIf
和ngIfElse
两个模板输入变量
当使用*ngFor
或*ngSwitch
的时候也会发生类似转换的过程。
那么,当我们在一个元素上使用两个结构指令的微语法会咋样?类似于:
1 | <div *ngIf="!loading" *ngFor="let item of dataList" ></div> |
Uncaught Error: Template parse errors:
Can’t have multiple template bindings on one element. Use only one attribute
1 | 我们需要拆开来展示: |
但这样会多出来一层div,在设置样式的时候会很别扭。
那么是不是有个方法我们可以不用增加额外的元素呢?那该祭出ng-container
指令了。😏
ng-container
ng-container
指令为我们提供了一个元素,我们可以将结构型指令应用在这个标签上,而不用增加额外的元素。
解决上面的多指令的问题:
1 | <ng-container *ngIf="!loading"> |
ng-container
指令还有另一个主要用途:提供一个占位符,用于将模板动态注入到页面。
可以想象为一个模板的插槽,配合ngTemplateOutlet
指令,我可以很方便的展示渲染模板。
我们前面创建的loadingTemp
只有在*ngIf
中判断条件后可以渲染,那么我们希望可以直接渲染,不需要任何判断,那么可以:
1 | <ng-container *ngTemplateOutlet="loadingTemp"></ng-container> |
模板上下文
关于模板的一个关键问题是,模板内部可见什么?
模板是否有自己单独的变量范围?模板可以看到哪些变量?
在ng-template
标记的主体内,我们可以访问外部模板中可见的上下文变量,也就是所有模板中可以访问的组件变量,在申明的模板内部都是可以访问的。
但是,每个模板也可以定义自己的一组输入变量。
实际上,每个模板都有一个关联的上下文对象,该上下文对象包含所有模板特定的输入变量。例如,我们有个相册列表:
1 | <div class="album-list"> |
我们来看看这里发生的事情:
- 创建了
albumTemp
模板,并声明接收模板变量album
,命名为albumInfo
。 - 模板输入变量名为
albumInfo
,通过ng-template
的属性前缀let-
来定义了这个变量。 - 变量
albumInfo
在模板albumTemp
内部可用,外部不可用(不污染组件变量空间)。 - 变量的内容由
let-albumInfo
属性的表达式确定。 - 表达式对上下文对象进行评估,语模板一起传递给
ngTemplateOutlet
进行实例化。 - 然后上下文对象必须具有一个名为
album
的属性的对象,才能在模板中显示值。 - 上下文对象通过
ngTemplateOutlet
指令的context
属性传递给模板。
模板在组件中的使用
我们可以使用模板引用变量在组件的模板中使用,也可以使用ViewChild
装饰器将模板在组件中使用:
1 | 'albumTemp') private albumTempRef: TemplateRef<any>; ( |
这意味着模板可以在组件类中使用,那么我们也可以将它传递给子组件。这样的话,更方便我们创建定制化的组件,该组件不仅可以传递数据,也可以将模板传入。
假设我们有组件A,和子组件AChild,通过在A组件中定义模板userTemp
,然后通过@Input
传递给子组件AChild,然后在子组件中通过*ngTemplateOutlet
来渲染模板。
1 | import {Component, Input, TemplateRef} from "@angular/core"; |
这种设计的场景是,让组件的消费者可以自定义组件的内容,方便扩展。
参考链接:
https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet/