希望是最好懂的 Angular Pipe 介紹

Ray
17 min readJul 30, 2023

--

img src

Pipe是一種轉換的簡單函數,用來接收輸入值並返回轉換後的值。

本文內容

  1. #說明
  2. #如何自訂 Pipe
  3. #內建常用 Pipe 介紹

#說明

基本用法

透過 Pipe 將傳入參數轉為大寫後輸出

<p>{{'hello world' | uppercase}}</p> 
// HELLO WORLD
// (uppercase為Angular內建Pipe,不需要特別導入即可使用)

那這樣跟在 HTML 上直接呼叫函數或是套用Function有什麼不同嗎?

<p>{{'hello world'.toUpperCase()}}</p>
// HELLO WORLD
<p>{{upperCase('hello world')}}</p>

upperCase(x: string) {
return x.toUpperCase();
}

以結果來看是沒有不同,因為都將輸入的值改為大寫後輸出。

差別在於,使用Pipe的話僅有當值發生變化時Angular會去調用函數來返回輸出值;若使用非Pipe的話,則是在每個變更檢測時都會呼叫函數,若使用非Pipe,當函數是比較複雜的話,可能會導致效能問題。

如何使用多個Pipe?

birthday = new Date(1998, 8, 29);
<p>{{birthday | date }}</p>
// Sep 29, 1998

只需要於後面再加上第二個Pipe即可

<p>{{birthday | date | uppercase }}</p>
// SEP 29, 1998

如何條件使用Pipe?

可以透過三元運算符

showUpperCase = true;
<p>{{showUpperCase ? ('ray' | uppercase) : ('Ray') | lowercase}}</p>
// RAY

showUpperCase = false;
<p>{{showUpperCase ? ('ray' | uppercase) : ('Ray') | lowercase}}</p>
// ray

#如何自訂 Pipe

基本用法

// 於 Angular CLI 輸入指令建立 Pipe
ng g pipe <pipe名稱> // ex. ng g pipe daysOfWeek

接著Angular會幫你建立Pipe檔案並匯入到所屬Module中

// app.module
import { DaysOfWeekPipe } from '.../days-of-week.pipe';

@NgModule({
declarations: [
...,
DaysOfWeekPipe
],
imports: [
...
],
providers: [],
bootstrap: [...]
})
export class AppModule { }

回到剛剛建立的.Pipe檔案,name是未來使用時須填入的管道名稱,可修改

接著寫入我們要的Pipe function,這邊範例是輸入1~7回傳對應星期的英文

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'daysOfWeek'
})
export class DaysOfWeekPipe implements PipeTransform {

transform(value: unknown, ...args: unknown[]): string {
switch (value) {
case 1:
return "Monday";
case 2:
return "Tuesday";
case 3:
return "Wednesday";
case 4:
return "Thursday";
case 5:
return "Friday";
case 6:
return "Saturday";
case 7:
return "Sunday";
default:
return "exception!";
}
}
}
<p>{{1 | daysOfWeek}}</p> 
// Monday

如何接收傳入與傳入參數?

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'daysOfWeek'
})
export class DaysOfWeekPipe implements PipeTransform {

transform(value: unknown, prefix: string): string {
switch (value) {
case 1:
return prefix + "Monday";
case 2:
return prefix + "Tuesday";
case 3:
return prefix + "Wednesday";
case 4:
return prefix + "Thursday";
case 5:
return prefix + "Friday";
case 6:
return prefix + "Saturday";
case 7:
return prefix + "Sunday";
default:
return prefix + "exception!";
}
}

}
<p>{{1 | daysOfWeek : '今天是:'}}</p>
// 今天是:Monday

多個參數範例

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'daysOfWeek'
})
export class DaysOfWeekPipe implements PipeTransform {

transform(value: unknown, prefix: string, upperCase: boolean): string {
switch (value) {
case 1:
return prefix + (upperCase ? "Monday".toUpperCase() : "Monday");
case 2:
return prefix + (upperCase ? "Tuesday".toUpperCase() : "Tuesday");
case 3:
return prefix + (upperCase ? "Wednesday".toUpperCase() : "Wednesday");
case 4:
return prefix + (upperCase ? "Thursday".toUpperCase() : "Thursday");
case 5:
return prefix + (upperCase ? "Friday".toUpperCase() : "Friday");
case 6:
return prefix + (upperCase ? "Saturday".toUpperCase() : "Saturday");
case 7:
return prefix + (upperCase ? "Sunday".toUpperCase() : "Sunday");
default:
return prefix + "exception!";
}
}

}
<p>{{5 | daysOfWeek : '今天是:':true}}</p>
// 今天是:FRIDAY

<p>{{3 | daysOfWeek : '今天是:':false}}</p>
// 今天是:Wednesday

Pipe的”pure”可選參數作用是什麼?

@Pipe({
name: 'pipeName',
pure: true
})

pure 參數預設為 true,也就是當使用 Pipe 時傳入的值發生改變時才會執行當前 pipe 的 function 去輸出對應的資料,但是有些情況下 Pipe 無法偵測到值是否改變,這時候若還是需要使用 Pipe 則需將 pure 更改為 false ,讓每次變更檢測時都強制執行 Pipe 的 function,但這樣做存在效能問題。

當傳入的是Json格式資料,Key的值發生異動時則無法被正常偵測到,以下範例是當按鈕被點擊時,會在user.name後面增加一個 "!",使得原本的 Ray(user.name) 變成 Ray!(user.name),接著透過兩個 span 來比對一個值的差異

import { Component } from '@angular/core';

@Component({
selector: 'app-d2ex2',
template: `
<button (click)="changeData()">更改 user 名稱</button>
<span>當前 user.name 值為: {{user.name}} </span>
<span>透過 Pipe 轉換後的值為: {{ user | pureTest}}</span>
`,
styleUrls: ['./d2ex2.component.scss']
})
export class D2ex2Component {

user = {
name: "Ray"
}

changeData() {
this.user.name += '!';
}


}

根據這個操作會發現當 Pipe 沒有執行

此時將 Pipe pure參數更改為 false

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'pureTest',
pure: false
})
export class PureTestPipe implements PipeTransform {

transform(value: any, ...args: unknown[]): unknown {
return '使用者是:' + value.name;
}

}

再操作一次會發現已經可以正常執行,這是因為將 Pipe pure值改為 false,使得每次變更檢測都會執行 Pipe,這樣同時也表示存在效能隱患。

#內建常用 Pipe 介紹

貨幣 (CurrencyPipe)

currencyCode:貨幣代碼 (String) [參考 ISO4217]

display:是否顯示貨幣前綴 (Boolean,String)

digitsInfo:數字顯示方式 (String)

{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}

  • minIntegerDigits:小數點前的最小整數位數。默認值為1
  • minFractionDigits:小數點後的最少位數。默認值為2
  • maxFractionDigits:小數點後的最大位數。默認值為2

(要注意若小數點超過maxFractionDigits的位數時,會被四捨五入)

<!-- 無傳入參數 -->
<p> {{9453 | currency}}</p> <!-- $9,453.00 -->

<!-- currencyCode -->
<p> {{9453 | currency:'TWD':true}}</p> <!-- NT$9,453.00 -->
<p> {{9453 | currency:'TWD':'TWD'}}</p> <!-- TWD9,453.00 -->

<!-- digitsInfo -->
<p> {{987.1234 | currency:'TWD':'TWD':'4.1-3'}}</p> <!-- TWD0,987.123 -->

十進制 (DecimalPipe)
digitsInfo:數字顯示方式 (String)

{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}

  • minIntegerDigits:小數點前的最小整數位數。默認值為 1。
  • minFractionDigits:小數點後的最少位數。默認值為 0。
  • maxFractionDigits:小數點後的最大位數。默認值為 3。

(要注意若小數點超過maxFractionDigits的位數時,會被四捨五入)

<!-- digitsInfo-->
<p> {{10.984 | number :'1.1-2'}}</p> <!-- 10.99 -->
<p> {{10.985 | number :'1.1-2'}}</p> <!-- 10.98 -->

數字轉百分比 (PercentPipe)
digitsInfo:數字顯示方式 (String)

{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}

  • minIntegerDigits:小數點前的最小整數位數。默認值為 1。
  • minFractionDigits:小數點後的最少位數。默認值為 0。
  • maxFractionDigits:小數點後的最大位數。默認值為 0。

(要注意若小數點超過maxFractionDigits的位數時,會被四捨五入)

<p> {{0.6 | percent}}</p></p> <!-- 60% -->

<p> {{0.6 | percent:'3.3-5'}}</p></p> <!-- 060.000% -->
<p> {{0.69875555 | percent:'3.3-5'}}</p></p> <!-- 069.87556% -->
<p> {{0.69875554 | percent:'3.3-5'}}</p> <!-- 069.87555% -->

日期 (DatePipe)

formatstring : 格式 (String)

timezonestring : 時區 (String)

// d = Date.now();

<!-- 無傳入參數 -->
<p> {{d | date}}</p> <!-- Aug 2, 2023 -->
<!-- formatstring -->
<p> {{d | date:'yyyyMMdd'}}</p> <!-- Aug 2, 2023 -->
<!-- timezonestring -->
<p> {{d | date:'short':'UTC +8'}}</p> <!-- 8/2/23, 8:00 PM-->

Json匹配選擇器 (I18nSelectPipe)

需要在 ts 中先建立 Json 格式的對應表,I18nSelectPipe 會將輸入的值對應到建立的表中,”other” 為所有項目皆不符合時的輸出項目。

// nameMap = { 'Ray': 'is Ray', 'Tom': 'is Tom', 'other': 'who?' };


<p>{{'Ray' | i18nSelect: nameMap}}</p> <!-- is Ray -->
<p>{{'a' | i18nSelect: nameMap}}</p> <!-- who? -->

Json轉換 (JsonPipe )

可以將傳入的 Object ,在畫面上顯示,通常用於Debug。

// obj = { name: 'Ray' , gender: 'Male'}

<p>未使用json pipe 傳入 obj:{{obj }}</p>
<p>使用json Pipe 傳入 obj:{{obj | json}}</p>
JsonPipe

Json鍵值 (KeyValuePipe)

可以取得 Json 中的每筆 Key , Value

//obj = { name: 'Ray', gender: 'Male' }

<li *ngFor="let item of (obj | keyvalue)">
{{ item.key }} : {{item.value}}
</li>
KeyValuePipe

字串轉小寫 (LowerCasePipe)

<p> {{'RAY' | lowercase}}</p> <!-- ray -->

字串轉大寫 (UpperCasePipe)

<p> {{'ray' | uppercase}}</p> <!-- RAY -->

首字母大寫 (TitleCasePipe)

<p> {{'anGUlaR pIpe' | titlecase}}</p> <!-- Angular Pipe -->

字串切片 (SlicePipe)

主要用法跟 Substring 差不多,可參考 String.prototype.substring()

<p> {{'AngularPipe' | slice:0:4}}</p> <!-- Angu(從第0位取到第4位) --> 
<p> {{'AngularPipe' | slice:-4}}</p> <!-- Pipe(從末前4位開始取)-->
<p> {{'AngularPipe' | slice:-4:-2}}</p><!-- Pi(從末前4位開始取到末2)-->

非同步 (AsyncPipe)

當需要在畫面上顯示 observable 類型的值時,可以使用 AsyncPipe 。AsyncPipe 會自動訂閱這個 observable,且當發出新值時顯示到畫面上,最後當組件被銷毀時也會同步取消訂閱,避免內存洩漏。

想一下,目前需求是:「透過建立一個每秒回傳一次當前時間的 observer,並要將值顯示在畫面上」應該如何寫?

先來看不使用 AsyncPipe,要將非同步值顯示於畫面上時,程式碼的寫法

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, Observer, Subscription } from 'rxjs';

@Component({
selector: 'app-d2ex2',
template: '<div>Time: {{ timeVal }}</div>',
styleUrls: ['./d2ex2.component.scss']
})
export class D2ex2Component implements OnInit, OnDestroy {

subscription: Subscription | undefined;
timeVal: string | undefined;

time$ = new Observable<string>((observer: Observer<string>) => {
setInterval(() => observer.next(new Date().toString()), 1000);
});

ngOnInit() {
this.subscription = this.time$.subscribe(val => {
this.timeVal = val;
})
}

ngOnDestroy() {
this.subscription?.unsubscribe();
}

}

再來看一下,使用 AsyncPipe,將非同步值顯示於畫面上,程式碼寫法

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, Observer, Subscription } from 'rxjs';

@Component({
selector: 'app-d2ex2',
template: '<div>Time: {{ time$ | async }}</div>',
styleUrls: ['./d2ex2.component.scss']
})
export class D2ex2Component {

time$ = new Observable<string>((observer: Observer<string>) => {
setInterval(() => observer.next(new Date().toString()), 1000);
});

}

因為 AsyncPipe 幫我們處理了訂閱以及取消訂閱的事情,所以讓程式碼簡短了很多~

--

--

No responses yet