Pipe是一種轉換的簡單函數,用來接收輸入值並返回轉換後的值。
本文內容
- #說明
- #如何自訂 Pipe
- #內建常用 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
:小數點前的最小整數位數。默認值為1minFractionDigits
:小數點後的最少位數。默認值為2maxFractionDigits
:小數點後的最大位數。默認值為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>
Json鍵值 (KeyValuePipe)
可以取得 Json 中的每筆 Key , Value
//obj = { name: 'Ray', gender: 'Male' }
<li *ngFor="let item of (obj | keyvalue)">
{{ item.key }} : {{item.value}}
</li>
字串轉小寫 (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 幫我們處理了訂閱以及取消訂閱的事情,所以讓程式碼簡短了很多~