开发 Angular 应用已经有一段时间了!!但是一直没怎么读过 Angular 的官方文档,基本都是再遇到问题的时候才去翻一翻。以至于每次都说要写公共组件,但最后写出来的公共组件中一堆特定场景的判断,导致这些公共组件不仅逻辑混乱,且只能在特定的项目中使用。而如果需要再增加一种场景,又得去改公共组件,然后又得全量测试...终于,在一个全新的项目中,受够了每次都要去改公共组件的折磨,研究了一下 Angular 中的"插槽",直呼真香。
插槽
插槽这个概念,对我而言,是从 vue 文档中知道的(很遗憾,工作中没怎么用过 vue)。它的作用是在组件内部留一个或多个插槽位置,可供组件传对应的模板代码进去。
Angular 中的插槽
Angular 中提供了 ng-content、ng-template 标签实现插槽的功能。
- ng-content
ng-content 的定义是指定在组件模板内投影内容的位置。
怎么理解呢,ng-content 可以直接对标 vue 中的 v-slot。ng-content 有个属性 select,当提供了 select 属性时,它是一个具名插槽,ng-content 会投影 select 选择器对应的内容,如果不设置则是默认插槽。具体使用如下:
<child>
这是一个默认插槽
<ng-content></ng-content>
这是一个具名插槽
<ng-content select=".select"></ng-content>
</child>
select 的合法值是 CSS 选择器
select = "head"; // 表示选择第一个head标签
select = ".select"; // 表示选择第一个class="select"的标签
select = "#select"; // 表示选择第一个id="select"的标签
select = "[name=select]"; // 表示选择第一个name="select"的标签
select = "[slot=select]"; // 表示选择第一个slot="select"的标签
- ng-template
ng-content 可以实现插槽的功能,但是它有一个缺点就是要么它是一个默认插槽,要么是一个特定的选择器。如果场景比较复杂,要定义的选择器不是固定的,则 ng-content 就无法满足需求了。此时,ng-template 就可以派上用场了。
ng-template 的作用是定义一个默认不渲染的模板。
就如它的字面意思,模板。用它定义的模板,可以用于作用域内的任何地方,且它本身不会增加 DOM 的层级。利用它并结合 ngTemplateOutlet 和 ngTemplateOutletContext 这两个指令,就可以实现复杂的插槽场景了。
ngTemplateOutlet:定义模板引用和模板上下文对象的字符串。
ngTemplateOutletContext:定义要附加到模板的上下文对象。使用它,我们可以在 template 上定义引用环境的上下文。
以最近在工作中遇到的一个组件举例:
因为业务是偏向于管理系统,用到表格的地方很多,但是使用的组件库中的表格配置项较多,且很多都是固定配置,如果不自己封装一个公共 table 组件,则每次使用都要写大量的重复代码。因此,二次封装这个组件很有必要。
使用 ng-template 插槽的代码如下:
// 子组件,定义插槽
<ng-template
[ngTemplateOutlet]="custom"
[ngTemplateOutletContext]="{
data: data
}"
></ng-template>
// 父组件
<child [custom]="custom">
<ng-template #custom let-data="data"> {{data.name}} </ng-template>
</child>
此处有个需要注意的点,单纯这样写在子组件是拿不到 custom 模板的,需要将这个模板赋值给一个变量然后传递给子组件。也即此处传递给 child 的 custom 不是直接对应#custom 这个 ng-template 的,而是先通过 viewChild 拿到#custom 这个视图,再把这个视图传给 child 组件。在父组件的 ts 中写如下代码:
@ViewChild('custom') custom: TemplateRef<any>;
按如上写好之后,在每次调用 child 组件时,在#custom 的 ng-template 中,具体怎么显示,需要哪些逻辑,都是由使用 child 的父组件来定的。在实际的项目中,基本都会在 child 中将通用和默认部分定义好,剩下的就由父组件去扩展了。只要不动 child 内部的通用和默认部分的逻辑,不管后续还需要增加什么功能,要显示什么结构,都只是在对应的父组件中修改,不用再去 child 这个公共组件中添加对应的判断逻辑而导致 child 组件的逻辑一团乱麻了。同时因为没有改通用的组件,只是改父组件自己的逻辑,不仅极大的提高了开发的效率,而且也减少了大量测试的工作量。