# 管道
`pipe`管道在大部分框架中又被稱為`filter`過濾器。無論是管道還是過濾器,只是叫法不同而且,它們的作用都是用來進行數據的轉換。
在中學的化學實驗中,我們在進行物質的過濾的時候接觸過過濾器。有村里蓋房子的時候,我們通常要把沙子中的過大的顆粒過濾出來,也接觸過過濾器。但在現實生活中,過濾器往往只會把多的東西過濾少了,且過濾后的物質必然存在于過濾前的物質之中。而我們此時提的過濾器,功能遠遠不止不此。它不止可以把多的過濾少了,把粗的過濾精了,還可以把少的過濾多了,把有的過濾沒了,甚至于把一種物質過濾成另一種物質。
> 計算機的過濾器的作用可以描述為:`把原有的數據按固定的規則轉換成另外一種數據`。
在Angular中只所以將這種數據格式轉換的功能稱為`管道`,我想是為了突顯多`管道`連接的特點。在Angular中,我們可將多個`管道`連接在一起使用。數據由第一個管道的入口進去,最終由最后一個管道的出口出來。在現實生活中,我們鋪設的管道也是將一節節短的管道首尾連接,最終形成長長的管道線的。

如上為石油管理,石油由管道的一頭流入,其依次通過每一節管道后,最終由管道的那一頭流出。
## 初始化性別管道
我們在上節的作業中,要求大家將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: [
```

## 初識管道
在管道中僅存在一個`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`來啟動個人中心組件后,得到了如下錯誤提示:

這是由于當前啟動的動態測試組件中并沒有聲明`sexPipe`,所以在解析V層的時發現了不在自己能力范圍內的`sex`管道。按我們在前面剛剛總結的模塊的組成,發現不難解決該問題:

```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();
});
```
最終測試結果如下:

可見管道中的`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 '女';
+ }
}
}
```

## 不挖坑
不怕神一樣的對手,就怕豬一樣的隊友。如果你在類似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'
);
}
```

## 單元測試
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`中給出的值相同,則方法被正確執行;否則將會拋出異常并中斷執行。這種處理方式保障了方法的結果與我們的預期是相同的,間接的證明了方法的正確性。

## 本節作業
我們在填坑的過程中,刪除了`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 發布部署
- 第九章 總結