管道用來對(duì)字符串、貨幣金額、日期和其他顯示數(shù)據(jù)進(jìn)行轉(zhuǎn)換和格式化。管道是一些簡單的函數(shù),可以在模板表達(dá)式中用來接受輸入值并返回一個(gè)轉(zhuǎn)換后的值。例如,你可以使用一個(gè)管道把日期顯示為 1988 年 4 月 15 日,而不是其原始字符串格式。
Angular 為典型的數(shù)據(jù)轉(zhuǎn)換提供了內(nèi)置的管道,包括國際化的轉(zhuǎn)換(i18n),它使用本地化信息來格式化數(shù)據(jù)。數(shù)據(jù)格式化常用的內(nèi)置管道如下:
DatePipe
:根據(jù)本地環(huán)境中的規(guī)則格式化日期值。UpperCasePipe
:把文本全部轉(zhuǎn)換成大寫。LowerCasePipe
:把文本全部轉(zhuǎn)換成小寫。CurrencyPipe
:把數(shù)字轉(zhuǎn)換成貨幣字符串,根據(jù)本地環(huán)境中的規(guī)則進(jìn)行格式化。DecimalPipe
:把數(shù)字轉(zhuǎn)換成帶小數(shù)點(diǎn)的字符串,根據(jù)本地環(huán)境中的規(guī)則進(jìn)行格式化。PercentPipe
:把數(shù)字轉(zhuǎn)換成百分比字符串,根據(jù)本地環(huán)境中的規(guī)則進(jìn)行格式化。你還可以創(chuàng)建管道來封裝自定義轉(zhuǎn)換,并在模板表達(dá)式中使用自定義管道。
要想使用管道,你應(yīng)該對(duì)這些內(nèi)容有基本的了解:
要應(yīng)用管道,請(qǐng)如下所示在模板表達(dá)式中使用管道操作符(|
),緊接著是該管道的名字,對(duì)于內(nèi)置的 DatePipe
它的名字是 date
。這個(gè)例子中的顯示如下:
"app.component.html" 在另一個(gè)單獨(dú)的模板中使用 date
來顯示生日。
"hero-birthday1.component.ts" 使用相同的管道作為組件內(nèi)嵌模板的一部分,同時(shí)該組件也會(huì)設(shè)置生日值。
<p>The hero's birthday is {{ birthday | date }}</p>
import { Component } from '@angular/core';
@Component({
selector: 'app-hero-birthday',
template: `<p>The hero's birthday is {{ birthday | date }}</p>`
})
export class HeroBirthdayComponent {
birthday = new Date(1988, 3, 15); // April 15, 1988 -- since month parameter is zero-based
}
可以用可選參數(shù)微調(diào)管道的輸出。例如,你可以使用 CurrencyPipe
和國家代碼(如 EUR
)作為參數(shù)。模板表達(dá)式 {{ amount
| currency:'EUR'
}} 會(huì)把 amount
轉(zhuǎn)換成歐元。緊跟在管道名稱( currency
)后面的是冒號(hào)(:
)和參數(shù)值('EUR'
)。
如果管道能接受多個(gè)參數(shù),就用冒號(hào)分隔這些值。例如,{{ amount | currency:'EUR':'Euros '}
} 會(huì)把第二個(gè)參數(shù)(字符串 'Euros '
)添加到輸出字符串中。你可以使用任何有效的模板表達(dá)式作為參數(shù),比如字符串字面量或組件的屬性。
有些管道需要至少一個(gè)參數(shù),并且允許使用更多的可選參數(shù),比如 SlicePipe
。例如, {{ slice:1:5 }}
會(huì)創(chuàng)建一個(gè)新數(shù)組或字符串,它以第 1 個(gè)元素開頭,并以第 5 個(gè)元素結(jié)尾。
下面的例子顯示了兩種不同格式('shortDate'
和 'fullDate'
)之間的切換:
該 "app.component.html" 模板使用 DatePipe
(名為 date
)的格式參數(shù)把日期顯示為 04/15/88 。
"hero-birthday2.component.ts" 組件把該管道的 format
參數(shù)綁定到 template
中組件的 format
屬性,并添加了一個(gè)按鈕,其 click
事件綁定到了該組件的 toggleFormat()
方法。
"hero-birthday2.component.ts" 組件的 toggleFormat()
方法會(huì)在短格式('shortDate'
)和長格式('fullDate'
)之間切換該組件的 format
屬性。
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
template: `
<p>The hero's birthday is {{ birthday | date:format }}</p>
<button (click)="toggleFormat()">Toggle Format</button>
`
export class HeroBirthday2Component {
birthday = new Date(1988, 3, 15); // April 15, 1988 -- since month parameter is zero-based
toggle = true; // start with true == shortDate
get format() { return this.toggle ? 'shortDate' : 'fullDate'; }
toggleFormat() { this.toggle = !this.toggle; }
}
點(diǎn)擊 Toggle Format 按鈕可以在 04/15/1988 和 Friday, April 15, 1988 之間切換日期格式,如下所示:
你可以對(duì)管道進(jìn)行串聯(lián),以便一個(gè)管道的輸出成為下一個(gè)管道的輸入。
在下面的示例中,串聯(lián)管道首先將格式應(yīng)用于一個(gè)日期值,然后將格式化之后的日期轉(zhuǎn)換為大寫字符。 "src/app/app.component.html" 模板的第一個(gè)標(biāo)簽頁把 DatePipe
和 UpperCasePipe
的串聯(lián)起來,將其顯示為 APR 15, 1988。"src/app/app.component.html" 模板的第二個(gè)標(biāo)簽頁在串聯(lián) uppercase
之前,還把 fullDate
參數(shù)傳遞給了 date
,將其顯示為 FRIDAY, APRIL 15, 1988。
The chained hero's birthday is
{{ birthday | date | uppercase}}
The chained hero's birthday is
{{ birthday | date:'fullDate' | uppercase}}
創(chuàng)建自定義管道來封裝那些內(nèi)置管道沒有提供的轉(zhuǎn)換。然后你就可以在模板表達(dá)式中使用你的自定義管道,就像內(nèi)置管道一樣,把輸入值轉(zhuǎn)換成顯示輸出。
要把類標(biāo)記為管道并提供配置元數(shù)據(jù),請(qǐng)把 @Pipe
裝飾器應(yīng)用到這個(gè)類上。管道類名是 UpperCamelCase
(類名的一般約定),相應(yīng)的 name
字符串是 camelCase
的。不要在 name
中使用連字符。詳細(xì)信息和更多示例,請(qǐng)參閱管道名稱 。
在模板表達(dá)式中使用 name
就像在內(nèi)置管道中一樣。
- 把你的管道包含在
NgModule
元數(shù)據(jù)的declarations
字段中,以便它能用于模板。
- 把你的管道包含在
NgModule
元數(shù)據(jù)的declarations
字段中,以便它能用于模板。
在自定義管道類中實(shí)現(xiàn) PipeTransform
接口來執(zhí)行轉(zhuǎn)換。
Angular 調(diào)用 transform
方法,該方法使用綁定的值作為第一個(gè)參數(shù),把其它任何參數(shù)都以列表的形式作為第二個(gè)參數(shù),并返回轉(zhuǎn)換后的值。
在游戲中,你可能希望實(shí)現(xiàn)一種指數(shù)級(jí)轉(zhuǎn)換,以指數(shù)級(jí)增加英雄的力量。例如,如果英雄的得分是 2,那么英雄的能量會(huì)指數(shù)級(jí)增長 10 次,最終得分為 1024。你可以使用自定義管道進(jìn)行這種轉(zhuǎn)換。
下列代碼示例顯示了兩個(gè)組件定義:
"exponential-strength.pipe.ts" 通過一個(gè)執(zhí)行轉(zhuǎn)換的 transform
方法定義了一個(gè)名為 exponentialStrength
的自定義管道。它為傳遞給管道的參數(shù)定義了 transform
方法的一個(gè)參數(shù)(exponent
)。
"power-booster.component.ts" 組件演示了如何使用該管道,指定了一個(gè)值( 2 )和一個(gè) exponent
參數(shù)( 10 )。
import { Pipe, PipeTransform } from '@angular/core';
/*
* Raise the value exponentially
* Takes an exponent argument that defaults to 1.
* Usage:
* value | exponentialStrength:exponent
* Example:
* {{ 2 | exponentialStrength:10 }}
* formats to: 1024
*/
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent?: number): number {
return Math.pow(value, isNaN(exponent) ? 1 : exponent);
}
}
import { Component } from '@angular/core';
@Component({
selector: 'app-power-booster',
template: `
<h2>Power Booster</h2>
<p>Super power boost: {{2 | exponentialStrength: 10}}</p>
`
})
export class PowerBoosterComponent { }
輸出結(jié)果如下所示:
你可以通過帶有管道的數(shù)據(jù)綁定來顯示值并響應(yīng)用戶操作。如果是原始類型的輸入值,比如 String
或 Number
,或者是對(duì)象引用型的輸入值,比如 Date
或 Array
,那么每當(dāng) Angular 檢測到輸入值或引用有變化時(shí),就會(huì)執(zhí)行該輸入管道。
比如,你可以修改前面的自定義管道示例,通過 ngModel
的雙向綁定來輸入數(shù)量和提升因子,如下面的代碼示例所示。
Path:"src/app/power-boost-calculator.component.ts" 。
import { Component } from '@angular/core';
@Component({
selector: 'app-power-boost-calculator',
template: `
<h2>Power Boost Calculator</h2>
<div>Normal power: <input [(ngModel)]="power"></div>
<div>Boost factor: <input [(ngModel)]="factor"></div>
<p>
Super Hero Power: {{power | exponentialStrength: factor}}
</p>
`
})
export class PowerBoostCalculatorComponent {
power = 5;
factor = 1;
}
每當(dāng)用戶改變 “normal power”
值或 “boost factor”
時(shí),就會(huì)執(zhí)行 exponentialStrength
管道,如下所示。
Angular 會(huì)檢測每次變更,并立即運(yùn)行該管道。對(duì)于原始輸入值,這很好。但是,如果要在復(fù)合對(duì)象中更改某些內(nèi)部值(例如日期中的月份、數(shù)組中的元素或?qū)ο笾械膶傩裕?,就需要了解變更檢測的工作原理,以及如何使用 impure
(非純)管道。
Angular 會(huì)在每次 DOM 事件(每次按鍵、鼠標(biāo)移動(dòng)、計(jì)時(shí)器滴答和服務(wù)器響應(yīng))之后運(yùn)行的變更檢測過程中查找對(duì)數(shù)據(jù)綁定值的更改。下面這段不使用管道的例子演示了 Angular 如何利用默認(rèn)的變更檢測策略來監(jiān)控和更新 heroes
數(shù)組中每個(gè)英雄的顯示效果。示例顯示如下:
*ngFor
會(huì)重復(fù)顯示英雄的名字。 New hero:
<input type="text" #box
(keyup.enter)="addHero(box.value); box.value=''"
placeholder="hero name">
<button (click)="reset()">Reset</button>
<div *ngFor="let hero of heroes">
{{hero.name}}
</div>
export class FlyingHeroesComponent {
heroes: any[] = [];
canFly = true;
constructor() { this.reset(); }
addHero(name: string) {
name = name.trim();
if (!name) { return; }
let hero = {name, canFly: this.canFly};
this.heroes.push(hero);
}
reset() { this.heroes = HEROES.slice(); }
}
每次用戶添加一個(gè)英雄時(shí),Angular 都會(huì)更新顯示內(nèi)容。如果用戶點(diǎn)擊了 Reset
按鈕,Angular 就會(huì)用原來這些英雄組成的新數(shù)組來替換 heroes
,并更新顯示。如果你添加刪除或更改了某個(gè)英雄的能力,Angular 也會(huì)檢測這些變化并更新顯示。
然而,如果對(duì)于每次更改都執(zhí)行一個(gè)管道來更新顯示,就會(huì)降低你應(yīng)用的性能。因此,Angular 會(huì)使用更快的變更檢測算法來執(zhí)行管道,如下一節(jié)所述。
通過默認(rèn)情況下,管道會(huì)定義成純的(pure
),這樣 Angular 只有在檢測到輸入值發(fā)生了純變更時(shí)才會(huì)執(zhí)行該管道。純變更是對(duì)原始輸入值(比如 String
、Number
、Boolean
或 Symbol
)的變更,或是對(duì)對(duì)象引用的變更(比如 Date
、Array
、Function
、Object
)。
純管道必須使用純函數(shù),它能處理輸入并返回沒有副作用的值。換句話說,給定相同的輸入,純函數(shù)應(yīng)該總是返回相同的輸出。
使用純管道,Angular 會(huì)忽略復(fù)合對(duì)象中的變化,例如往現(xiàn)有數(shù)組中新增的元素,因?yàn)闄z查原始值或?qū)ο笠帽葘?duì)對(duì)象中的差異進(jìn)行深度檢查要快得多。Angular 可以快速判斷是否可以跳過執(zhí)行該管道并更新視圖。
但是,以數(shù)組作為輸入的純管道可能無法正常工作。為了演示這個(gè)問題,修改前面的例子來把英雄列表過濾成那些會(huì)飛的英雄。在 *ngFor
中使用 FlyingHeroesPipe
,代碼如下。這個(gè)例子的顯示如下:
<div *ngFor="let hero of (heroes | flyingHeroes)">
{{hero.name}}
</div>
import { Pipe, PipeTransform } from '@angular/core';
import { Flyer } from './heroes';
@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
transform(allHeroes: Flyer[]) {
return allHeroes.filter(hero => hero.canFly);
}
}
該應(yīng)用現(xiàn)在展示了意想不到的行為:當(dāng)用戶添加了會(huì)飛的英雄時(shí),它們都不會(huì)出現(xiàn)在 “Heroes who fly” 中。發(fā)生這種情況是因?yàn)樘砑佑⑿鄣拇a會(huì)把它 push
到 heroes
數(shù)組中:
Path:"src/app/flying-heroes.component.ts" 。
this.heroes.push(hero);
而變更檢測器會(huì)忽略對(duì)數(shù)組元素的更改,所以管道不會(huì)運(yùn)行。
Angular 忽略了被改變的數(shù)組元素的原因是對(duì)數(shù)組的引用沒有改變。由于 Angular 認(rèn)為該數(shù)組仍是相同的,所以不會(huì)更新其顯示。
獲得所需行為的方法之一是更改對(duì)象引用本身。你可以用一個(gè)包含新更改過的元素的新數(shù)組替換該數(shù)組,然后把這個(gè)新數(shù)組作為輸入傳給管道。在上面的例子中,你可以創(chuàng)建一個(gè)附加了新英雄的數(shù)組,并把它賦值給 heroes
。 Angular 檢測到了這個(gè)數(shù)組引用的變化,并執(zhí)行了該管道。
總結(jié)一下,如果修改了輸入數(shù)組,純管道就不會(huì)執(zhí)行。如果替換了輸入數(shù)組,就會(huì)執(zhí)行該管道并更新顯示,如下圖所示。
上面的例子演示了如何更改組件的代碼來適應(yīng)某個(gè)管道。
為了讓你的組件更簡單,獨(dú)立于那些使用管道的 HTML,你可以用一個(gè)不純的管道來檢測復(fù)合對(duì)象(如數(shù)組)中的變化,如下一節(jié)所述。
要在復(fù)合對(duì)象內(nèi)部進(jìn)行更改后執(zhí)行自定義管道(例如更改數(shù)組元素),就需要把管道定義為 impure
以檢測非純的變更。每當(dāng)按鍵或鼠標(biāo)移動(dòng)時(shí),Angular 都會(huì)檢測到一次變更,從而執(zhí)行一個(gè)非純管道。
注:
- 雖然非純管道很實(shí)用,但要小心使用。長時(shí)間運(yùn)行非純管道可能會(huì)大大降低你的應(yīng)用速度。
通過把 pure
標(biāo)志設(shè)置為 false
來把管道設(shè)置成非純的:
Path:"src/app/flying-heroes.pipe.ts" 。
@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
下面的代碼顯示了 FlyingHeroesImpurePipe
的完整實(shí)現(xiàn),它擴(kuò)展了 FlyingHeroesPipe
以繼承其特性。這個(gè)例子表明你不需要修改其他任何東西 - 唯一的區(qū)別就是在管道元數(shù)據(jù)中把 pure
標(biāo)志設(shè)置為 false
。
@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
import { Pipe, PipeTransform } from '@angular/core';
import { Flyer } from './heroes';
@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
transform(allHeroes: Flyer[]) {
return allHeroes.filter(hero => hero.canFly);
}
}
對(duì)于非純管道,FlyingHeroesImpurePipe
是個(gè)不錯(cuò)的選擇,因?yàn)樗?transform
函數(shù)非常簡單快捷:
Path:"src/app/flying-heroes.pipe.ts (filter)" 。
return allHeroes.filter(hero => hero.canFly);
你可以從 FlyingHeroesComponent
派生一個(gè) FlyingHeroesImpureComponent
。如下面的代碼所示,只有模板中的管道發(fā)生了變化。
Path:"src/app/flying-heroes-impure.component.html (excerpt)" 。
<div *ngFor="let hero of (heroes | flyingHeroesImpure)">
{{hero.name}}
</div>
可觀察對(duì)象能讓你在應(yīng)用的各個(gè)部分之間傳遞消息。建議在事件處理、異步編程以及處理多個(gè)值時(shí)使用這些可觀察對(duì)象??捎^察對(duì)象可以提供任意類型的單個(gè)或多個(gè)值,可以是同步的(作為一個(gè)函數(shù)為它的調(diào)用者提供一個(gè)值),也可以是異步的。
使用內(nèi)置的 AsyncPipe
接受一個(gè)可觀察對(duì)象作為輸入,并自動(dòng)訂閱輸入。如果沒有這個(gè)管道,你的組件代碼就必須訂閱這個(gè)可觀察對(duì)象來使用它的值,提取已解析的值、把它們公開進(jìn)行綁定,并在銷毀這段可觀察對(duì)象時(shí)取消訂閱,以防止內(nèi)存泄漏。 AsyncPipe
是一個(gè)非純管道,可以節(jié)省組件中的樣板代碼,以維護(hù)訂閱,并在數(shù)據(jù)到達(dá)時(shí)持續(xù)從該可觀察對(duì)象中提供值。
下列代碼示例使用 async
管道將帶有消息字符串( message$
)的可觀察對(duì)象綁定到視圖中。
Path:"src/app/hero-async-message.component.ts" 。
import { Component } from '@angular/core';
import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';
@Component({
selector: 'app-hero-message',
template: `
<h2>Async Hero Message and AsyncPipe</h2>
<p>Message: {{ message$ | async }}</p>
<button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
message$: Observable<string>;
private messages = [
'You are my hero!',
'You are the best hero!',
'Will you be my hero?'
];
constructor() { this.resend(); }
resend() {
this.message$ = interval(500).pipe(
map(i => this.messages[i]),
take(this.messages.length)
);
}
}
為了使用 HTTP 與后端服務(wù)進(jìn)行通信,HttpClient
服務(wù)使用了可觀察對(duì)象,并提供了 HTTPClient.get()
方法來從服務(wù)器獲取數(shù)據(jù)。這個(gè)異步方法會(huì)發(fā)送一個(gè) HTTP 請(qǐng)求,并返回一個(gè)可觀察對(duì)象,它會(huì)發(fā)出請(qǐng)求到的響應(yīng)數(shù)據(jù)。
如 AsyncPipe
所示,你可以使用非純管道 AsyncPipe
接受一個(gè)可觀察對(duì)象作為輸入,并自動(dòng)訂閱輸入。你也可以創(chuàng)建一個(gè)非純管道來建立和緩存 HTTP 請(qǐng)求。
每當(dāng)組件運(yùn)行變更檢測時(shí)就會(huì)調(diào)用非純管道,在 CheckAlways
策略下會(huì)每隔幾毫秒運(yùn)行一次。為避免出現(xiàn)性能問題,只有當(dāng)請(qǐng)求的 URL 發(fā)生變化時(shí)才會(huì)調(diào)用該服務(wù)器(如下例所示),并使用該管道緩存服務(wù)器的響應(yīng)。顯示如下:
import { HttpClient } from '@angular/common/http';
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'fetch',
pure: false
})
export class FetchJsonPipe implements PipeTransform {
private cachedData: any = null;
private cachedUrl = '';
constructor(private http: HttpClient) { }
transform(url: string): any {
if (url !== this.cachedUrl) {
this.cachedData = null;
this.cachedUrl = url;
this.http.get(url).subscribe(result => this.cachedData = result);
}
return this.cachedData;
}
}
fetch
管道與內(nèi)置的 JsonPipe 串聯(lián)起來,以 JSON 格式顯示同一份英雄數(shù)據(jù)。 import { Component } from '@angular/core';
@Component({
selector: 'app-hero-list',
template: `
<h2>Heroes from JSON File</h2>
<div *ngFor="let hero of ('assets/heroes.json' | fetch) ">
{{hero.name}}
</div>
<p>Heroes as JSON:
{{'assets/heroes.json' | fetch | json}}
</p>`
})
export class HeroListComponent { }
在上面的例子中,管道請(qǐng)求數(shù)據(jù)時(shí)的剖面展示了如下幾點(diǎn):
fetch 和 fetch-json 管道會(huì)顯示英雄,如下圖所示。
注:
- 內(nèi)置的JsonPipe
提供了一種方法來診斷一個(gè)離奇失敗的數(shù)據(jù)綁定,或用來檢查一個(gè)對(duì)象是否能用于將來的綁定。
更多建議: