# 管道 `pipe`管道在大部分框架中又被称为`filter`过滤器。无论是管道还是过滤器,只是叫法不同而且,它们的作用都是用来进行数据的转换。 在中学的化学实验中,我们在进行物质的过滤的时候接触过过滤器。有村里盖房子的时候,我们通常要把沙子中的过大的颗粒过滤出来,也接触过过滤器。但在现实生活中,过滤器往往只会把多的东西过滤少了,且过滤后的物质必然存在于过滤前的物质之中。而我们此时提的过滤器,功能远远不止不此。它不止可以把多的过滤少了,把粗的过滤精了,还可以把少的过滤多了,把有的过滤没了,甚至于把一种物质过滤成另一种物质。 > 计算机的过滤器的作用可以描述为:`把原有的数据按固定的规则转换成另外一种数据`。 在Angular中只所以将这种数据格式转换的功能称为`管道`,我想是为了突显多`管道`连接的特点。在Angular中,我们可将多个`管道`连接在一起使用。数据由第一个管道的入口进去,最终由最后一个管道的出口出来。在现实生活中,我们铺设的管道也是将一节节短的管道首尾连接,最终形成长长的管道线的。 ![image-20210308085811791](https://img.kancloud.cn/65/eb/65eb0bc3d6384356452b1356ac87a680_1252x448.png) 如上为石油管理,石油由管道的一头流入,其依次通过每一节管道后,最终由管道的那一头流出。 ## 初始化性别管道 我们在上节的作业中,要求大家将true与false在输出时转换为男和女,这可以通过前面学习过的`ngIf`来快速实现。而使用管道的方法,也够使V层的代码更加的简介,也使得我们编写的代码更容易被测试,从而达到解耦以及提升代码健壮性的目的。 使用Angular Cli能够快速的初始化一个管道。在此,我们初始化一个在管道的这头输入true、false,在管道的那头输入男女的性别管道---- sexPipe。 该管道暂时只有个人中心组件使用,所以我们将其建立在个人中心组件的文件夹下,使用shell进入`src/app/personal-center`文件夹,并执行以下命令: ```bash panjiedeMacBook-Pro:personal-center panjie$ ng g p sex CREATE src/app/personal-center/sex.pipe.spec.ts (175 bytes) CREATE src/app/personal-center/sex.pipe.ts (211 bytes) UPDATE src/app/app.module.ts (1065 bytes) ``` 该过程与创建组件、类的过程基本相同,为我们创建一个主体文件,一个测试文件,同步更新了该pipe所在的模块。与组件性质相同,管道同样是模块的一部分,所以同样被声明在`declarations`中: ```typescript +++ b/first-app/src/app/app.module.ts @@ -11,6 +11,7 @@ import {RouterModule} from '@angular/router'; import {LoginComponent} from './login/login.component'; import {IndexComponent} from './index/index.component'; import { PersonalCenterComponent } from './personal-center/personal-center.component'; +import { SexPipe } from './personal-center/sex.pipe'; @NgModule({ @@ -20,7 +21,8 @@ import { PersonalCenterComponent } from './personal-center/personal-center.compo EditComponent, LoginComponent, IndexComponent, - PersonalCenterComponent + PersonalCenterComponent, + SexPipe ], imports: [ ``` ![image-20210308091837891](https://img.kancloud.cn/5e/00/5e0049efb5dee017420d8d31e0afca57_1392x532.png) ## 初识管道 在管道中仅存在一个`transform`方法: ```typescript transform(value: unknown, ...args: unknown[]): unknown { return null; } ``` 在方法的参数中:`value`做为流入值,流出值由方法中的`return`决定。比如无论注入什么,我们都流出`天职师大`,就可以这样写: ```typescript +++ b/first-app/src/app/personal-center/sex.pipe.ts @@ -6,7 +6,8 @@ import {Pipe, PipeTransform} from '@angular/core'; export class SexPipe implements PipeTransform { transform(value: unknown, ...args: unknown[]): unknown { - return null; + console.log('流入', value); + return '天职师大'; } } ``` 此时,我们便可以将该管道加入到相应的V层文件中了。在加入时,需要使用管道的标识,该标识位于管道文件的如下位置: ```typescript @Pipe({ name: 'sex' 👈 }) export class SexPipe implements PipeTransform { ``` 将该标识加到对应的V层代码中,则将自动调用该管道的`transform`方法完成数据的转换: ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.html @@ -28,7 +28,7 @@ 性别: </div> <div class="col-8"> - {{me.sex}} + {{me.sex | sex}} </div> </div> </div> ``` `|`又称为`管道符`,它就像一个竖直的管道。在该管道符后接管道标识`sex`,将使用`sexPipe`来过滤`me.sex`,最终在前台显示的为过滤器的值。 ## 测试 我们使用`ng t`来启动个人中心组件后,得到了如下错误提示: ![image-20210308092857993](https://img.kancloud.cn/58/a3/58a30828a1d5879073a84957c946fcea_1564x174.png) 这是由于当前启动的动态测试组件中并没有声明`sexPipe`,所以在解析V层的时发现了不在自己能力范围内的`sex`管道。按我们在前面刚刚总结的模块的组成,发现不难解决该问题: ![image-20210308091837891](https://img.kancloud.cn/5e/00/5e0049efb5dee017420d8d31e0afca57_1392x532.png) ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.spec.ts @@ -1,6 +1,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {PersonalCenterComponent} from './personal-center.component'; +import {SexPipe} from './sex.pipe'; describe('PersonalCenterComponent', () => { let component: PersonalCenterComponent; @@ -8,7 +9,7 @@ describe('PersonalCenterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [PersonalCenterComponent] + declarations: [PersonalCenterComponent, SexPipe] }) .compileComponents(); }); ``` 最终测试结果如下: ![image-20210308093040552](https://img.kancloud.cn/56/28/56281103e920e3c1a6ce30ff61093f63_1176x396.png) 可见管道中的`transform`方法被执行,成功的接收到了输入值,输出值也成功的显示在了V层上。 ## 完成功能 性别的过滤功能很简单,如果是true就返回男,如果是false便返回女: ```typescript +++ b/first-app/src/app/personal-center/sex.pipe.ts @@ -6,8 +6,11 @@ import {Pipe, PipeTransform} from '@angular/core'; export class SexPipe implements PipeTransform { transform(value: unknown, ...args: unknown[]): unknown { - console.log('流入', value); - return '天职师大'; + if (value === true) { + return '男'; + } else { + return '女'; + } } } ``` ![image-20210308093349596](https://img.kancloud.cn/a3/91/a39101e9bc46c46b53a1f97d2cb3da8b_608x260.png) ## 不挖坑 不怕神一样的对手,就怕猪一样的队友。如果你在类似LOL的游戏中也曾被猪一样的队友坑过,如果我们也不小心做过猪一样的队友。那么相信你绝对深知**不挖坑**的意义之所在。 其实大多数的时候,并不是我们的队友想挖坑,而是他不知不觉的就挖了坑。刚刚我们貌似完成了性别管道的功能,看其实不然。 比如该管道在`transform`方法的参数中将value声明为`unknown`未知类型,但实际上我们却只支持`boolean`类型;再比如`transform`方法将返回值类型同样声明为`unknown`未知类型,但实际上却为`string`类型。这些看似不起眼的类型不统一,会给队友造成极大的因扰。因为队友再调用该pipe时,仅仅关心输入与输出,并不会也不应该关心我们内部的实现原理。 所以我们应该显示的声明方法参数类型以及返回值类型,以供其它队友参考: ```typescript +++ b/first-app/src/app/personal-center/sex.pipe.ts @@ -5,7 +5,7 @@ import {Pipe, PipeTransform} from '@angular/core'; }) export class SexPipe implements PipeTransform { - transform(value: unknown, ...args: unknown[]): unknown { + transform(value: boolean): string { if (value === true) { return '男'; } else { ``` 解决了上述问题后,则应该充分的考虑变量的类型。在javascript中,有两个特殊的值`undefined`、`null`,一个代表未定义,另一个代表值为空。而我们的上述代码中并没有处理上述值。这将导致:将未设置性别的教师性别显示为女,为此我们进行如下修正: ```typescript +++ b/first-app/src/app/personal-center/sex.pipe.ts @@ -6,6 +6,11 @@ import {Pipe, PipeTransform} from '@angular/core'; export class SexPipe implements PipeTransform { transform(value: boolean): string { + if (value === undefined || value === null) { + console.warn('接收到了空的值'); + return '-'; + } + if (value === true) { return '男'; } else { ``` 如此以来,当传入的值为`undefined`、`null`时,便会在控制台打印一条警告信息的同时,输入`-`字符串。我们简单测试一下: ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.ts @@ -18,7 +18,7 @@ export class PersonalCenterComponent implements OnInit { 'zhangsan@yunzhi.club', '张三', 'password', - true, + null as unknown as boolean, 'zhangsan' ); } ``` ![image-20210308100442924](https://img.kancloud.cn/b8/2e/b82e16fdbcd58267e57b9adf161d24a7_1242x342.png) ## 单元测试 Angular Cli在生成`SexPipe`时,对应生成了测试文件 src/app/personal-center/sex.pipe.spec.ts。这说明:在进行管道功能的开发时,我们应该启动当前测试文件,而不是个人中心组件对应的测试文件。在Angular中,对管道进行单独的测试的方法最简单,效率最高,效果最好。下面,让我们将`fit`移至 src/app/personal-center/sex.pipe.spec.ts中: ```typescript +++ b/first-app/src/app/personal-center/sex.pipe.spec.ts @@ -1,7 +1,7 @@ import { SexPipe } from './sex.pipe'; describe('SexPipe', () => { - it('create an instance', () => { + fit('create an instance', () => { const pipe = new SexPipe(); expect(pipe).toBeTruthy(); }); ``` 值得一提的是由于管理只涉及到输入与输出,所以测试管道的代码与测试普通类的方法一致:new一个实例出来,调用相关的方法并对输出的值进行预测。 > 在软件开发中,我们把这种**预测**又常被称为**断言**,大概表求:我们**断**定某个结果、状态的语**言**。 我们根据输入、输出进行几组**预测**如下: ```typescript +++ b/first-app/src/app/personal-center/sex.pipe.spec.ts @@ -3,6 +3,14 @@ import { SexPipe } from './sex.pipe'; describe('SexPipe', () => { fit('create an instance', () => { const pipe = new SexPipe(); - expect(pipe).toBeTruthy();g + expect(pipe).toBeTruthy(); + // 预测输入true时,输出男 + expect(pipe.transform(true)).toBe('男'); + // 预测输入true时,输出男 + expect(pipe.transform(false)).toBe('女'); + // 预测输入undefined时,输出- + expect(pipe.transform(undefined as unknown as boolean)).toBe('-'); + // 预测输入null时,输出- + expect(pipe.transform(null as unknown as boolean)).toBe('-'); }); }); ``` 此时,我们执行`ng t`。若`expect`中函数执行的结果与`toBe`中给出的值相同,则方法被正确执行;否则将会抛出异常并中断执行。这种处理方式保障了方法的结果与我们的预期是相同的,间接的证明了方法的正确性。 ![image-20210308101633635](https://img.kancloud.cn/d1/5d/d15d70246f70db2465a3fb79f344e580_592x118.png) ## 本节作业 我们在填坑的过程中,删除了`transform(value: unknown, ...args: unknown[]):`中的参数:` ...args: unknown[]`,请查询`...args`代表的含义,并解释为什么删除了该参数也没有报任何语法错误。 | 名称 | 地址 | | | -------- | ------------------------------------------------------------ | ---- | | 管道 | [https://angular.cn/guide/pipes](https://angular.cn/guide/pipes) | | | 管道测试 | [https://angular.cn/guide/testing-pipes](https://angular.cn/guide/testing-pipes) | | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step4.2.zip](https://github.com/mengyunzhi/angular11-guild/archive/step4.2.zip) | |