# 管道
`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) | |
- 序言
- 第一章 Hello World
- 1.1 环境安装
- 1.2 Hello Angular
- 1.3 Hello World!
- 第二章 教师管理
- 2.1 教师列表
- 2.1.1 初始化原型
- 2.1.2 组件生命周期之初始化
- 2.1.3 ngFor
- 2.1.4 ngIf、ngTemplate
- 2.1.5 引用 Bootstrap
- 2.2 请求后台数据
- 2.2.1 HttpClient
- 2.2.2 请求数据
- 2.2.3 模块与依赖注入
- 2.2.4 异步与回调函数
- 2.2.5 集成测试
- 2.2.6 本章小节
- 2.3 新增教师
- 2.3.1 组件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 对接后台
- 2.3.4 路由
- 2.4 编辑教师
- 2.4.1 组件初始化
- 2.4.2 获取路由参数
- 2.4.3 插值与模板表达式
- 2.4.4 初识泛型
- 2.4.5 更新教师
- 2.4.6 测试中的路由
- 2.5 删除教师
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome图标库
- 2.6.3 firefox
- 2.7 总结
- 第三章 用户登录
- 3.1 初识单元测试
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 着陆组件
- 3.5 @Output
- 3.6 TypeScript 类
- 3.7 浏览器缓存
- 3.8 总结
- 第四章 个人中心
- 4.1 原型
- 4.2 管道
- 4.3 对接后台
- 4.4 x-auth-token认证
- 4.5 拦截器
- 4.6 小结
- 第五章 系统菜单
- 5.1 延迟及测试
- 5.2 手动创建组件
- 5.3 隐藏测试信息
- 5.4 规划路由
- 5.5 定义菜单
- 5.6 注销
- 5.7 小结
- 第六章 班级管理
- 6.1 新增班级
- 6.1.1 组件初始化
- 6.1.2 MockApi 新建班级
- 6.1.3 ApiInterceptor
- 6.1.4 数据验证
- 6.1.5 教师选择列表
- 6.1.6 MockApi 教师列表
- 6.1.7 代码重构
- 6.1.8 小结
- 6.2 教师列表组件
- 6.2.1 初始化
- 6.2.2 响应式表单
- 6.2.3 getTestScheduler()
- 6.2.4 应用组件
- 6.2.5 小结
- 6.3 班级列表
- 6.3.1 原型设计
- 6.3.2 初始化分页
- 6.3.3 MockApi
- 6.3.4 静态分页
- 6.3.5 动态分页
- 6.3.6 @Input()
- 6.4 编辑班级
- 6.4.1 测试模块
- 6.4.2 响应式表单验证
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定义FormControl
- 6.4.6 代码重构
- 6.4.7 小结
- 6.5 删除班级
- 6.6 集成测试
- 6.6.1 惰性加载
- 6.6.2 API拦截器
- 6.6.3 路由与跳转
- 6.6.4 ngStyle
- 6.7 初识Service
- 6.7.1 catchError
- 6.7.2 单例服务
- 6.7.3 单元测试
- 6.8 小结
- 第七章 学生管理
- 7.1 班级列表组件
- 7.2 新增学生
- 7.2.1 exports
- 7.2.2 自定义验证器
- 7.2.3 异步验证器
- 7.2.4 再识DI
- 7.2.5 属性型指令
- 7.2.6 完成功能
- 7.2.7 小结
- 7.3 单元测试进阶
- 7.4 学生列表
- 7.4.1 JSON对象与对象
- 7.4.2 单元测试
- 7.4.3 分页模块
- 7.4.4 子组件测试
- 7.4.5 重构分页
- 7.5 删除学生
- 7.5.1 第三方dialog
- 7.5.2 批量删除
- 7.5.3 面向对象
- 7.6 集成测试
- 7.7 编辑学生
- 7.7.1 初始化
- 7.7.2 自定义provider
- 7.7.3 更新学生
- 7.7.4 集成测试
- 7.7.5 可订阅的路由参数
- 7.7.6 小结
- 7.8 总结
- 第八章 其它
- 8.1 打包构建
- 8.2 发布部署
- 第九章 总结