模板中使用“|”操作符来使用管道:
<p>The hero's birthday is {{ birthday | date }}</p>
如果管道具有参数,用冒号:来分隔参数,具有多个参数,参数之间也用冒号来分隔,例如:
一参数:<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
管道的参数值可以是任何有效的模板表达式(参见模板语法中的模板表达式部分),比如字符串字面量或组件的属性。 换句话说,借助属性绑定,你也可以像用绑定来控制生日的值一样,控制生日的显示格式。
来写第二个组件,它把管道的格式参数绑定到该组件的 format
属性。这里是新组件的模板:
template: `
<p>The hero's birthday is {{ birthday | date:format }}</p>
<button (click)="toggleFormat()">Toggle Format</button>
你还能在模板中添加一个按钮,并把它的点击事件绑定到组件的 toggleFormat()
方法。 此方法会在短日期格式('shortDate'
)和长日期格式('fullDate'
)之间切换组件的 format
属性。
export class HeroBirthday2Component {
birthday = new Date(1988, 3, 15); // April 15, 1988
toggle = true; // start with true == shortDate
get format() { return this.toggle ? 'shortDate' : 'fullDate'; }
toggleFormat() { this.toggle = !this.toggle; }
}
链式管道
你可以把管道串联在一起,以组合出一些潜在的有用功能。 下面这个例子中,要把 birthday
串联到 DatePipe
管道,然后又串联到 UpperCasePipe
,这样就可以把生日显示成大写形式了。 生日被显示成了APR 15, 1988:
The chained hero's birthday is
{{ birthday | date | uppercase}}
下面这个显示FRIDAY, APRIL 15, 1988的例子用同样的方式链接了这两个管道,而且同时还给 date
管道传进去一个参数
The chained hero's birthday is
{{ birthday | date:'fullDate' | uppercase}}
自定义管道
你还可以写自己的自定义管道。 下面就是一个名叫 ExponentialStrengthPipe
的管道,它可以放大英雄的能力:
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);
}
}
在这个管道的定义中体现了几个关键点:
-
管道是一个带有“管道元数据(pipe metadata)”装饰器的类。
-
这个管道类实现了
PipeTransform
接口的transform
方法,该方法接受一个输入值和一些可选参数,并返回转换后的值。 -
当每个输入值被传给
transform
方法时,还会带上另一个参数,比如你这个管道就有一个exponent
(放大指数) 参数。 -
可以通过
@Pipe
装饰器来告诉 Angular:这是一个管道。该装饰器是从 Angular 的core
库中引入的。 -
这个
@Pipe
装饰器允许你定义管道的名字,这个名字会被用在模板表达式中。它必须是一个有效的 JavaScript 标识符。 比如,你这个管道的名字是exponentialStrength
。
请注意以下几点:
-
你使用自定义管道的方式和内置管道完全相同。
-
你必须把这个管道添加到
AppModule
的declarations
数组中。 -
你必须在
AppModule
的declarations
数组中包含这个管道。
管道与变更检测
Angular 通过变更检测过程来查找绑定值的更改,并在每一次 JavaScript 事件之后运行:每次按键、鼠标移动、定时器以及服务器的响应。 这可能会让变更检测显得很昂贵,但是 Angular 会尽可能降低变更检测的成本。
当使用管道时,Angular 会选用一种更简单、更快速的变更检测算法。
纯(pure)管道与非纯(impure)管道
有两类管道:纯的与非纯的。 默认情况下,管道都是纯的。以前见到的每个管道都是纯的。 通过把它的 pure
标志设置为 false
,你可以制作一个非纯管道。你可以像这样让 FlyingHeroesPipe
变成非纯的:
纯管道
Angular 只有在它检测到输入值发生了纯变更时才会执行纯管道。 纯变更是指对原始类型值(String
、Number
、Boolean
、Symbol
)的更改, 或者对对象引用(Date
、Array
、Function
、Object
)的更改。
Angular 会忽略(复合)对象内部的更改。 如果你更改了输入日期(Date
)中的月份、往一个输入数组(Array
)中添加新值或者更新了一个输入对象(Object
)的属性,它都不会调用纯管道。
这可能看起来是一种限制,但它保证了速度。 对象引用的检查是非常快的(比递归的深检查要快得多),所以 Angular 可以快速的决定是否应该跳过管道执行和视图更新。
因此,如果要和变更检测策略打交道,就会更喜欢用纯管道。 如果不能,你就可以转回到非纯管道。
非纯管道
Angular 会在每个组件的变更检测周期中执行非纯管道。 非纯管道可能会被调用很多次,和每个按键或每次鼠标移动一样频繁。
要在脑子里绷着这根弦,必须小心翼翼的实现非纯管道。 一个昂贵、迟钝的管道将摧毁用户体验。
你把它从 FlyingHeroesPipe
中继承下来,以证明无需改动内部代码。 唯一的区别是管道元数据中的 pure
标志。
非纯 AsyncPipe
Angular 的 AsyncPipe
是一个有趣的非纯管道的例子。 AsyncPipe
接受一个 Promise
或 Observable
作为输入,并且自动订阅这个输入,最终返回它们给出的值。
AsyncPipe
管道是有状态的。 该管道维护着一个所输入的 Observable
的订阅,并且持续从那个 Observable
中发出新到的值。
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)
);
}
}
这个 Async 管道节省了组件的样板代码。 组件不用订阅这个异步数据源,而且不用在被销毁时取消订阅(如果订阅了而忘了反订阅容易导致隐晦的内存泄露)。
一个非纯而且带缓存的管道
来写更多的非纯管道:一个向服务器发起 HTTP 请求的管道。
时刻记住,非纯管道可能每隔几微秒就会被调用一次。 如果你不小心点,这个管道就会发起一大堆请求“攻击”服务器
下面这个管道只有当所请求的 URL 发生变化时才会向服务器发起请求。它会缓存服务器的响应。 代码如下,它使用Angular http客户端来接收数据
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;
}
}
接下来在一个测试挽具组件中演示一下它,该组件的模板中定义了两个使用到此管道的绑定,它们都从 heroes.json
文件中取得英雄数据。
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 { }
JsonPipe
第二个绑定除了用到 FetchPipe
之外还链接了更多管道。 它通过串联上内置管道 JsonPipe
来把英雄数据显示成了 JSON 格式。
纯管道与纯函数
纯管道使用纯函数。 纯函数是指在处理输入并返回结果时,不会产生任何副作用的函数。 给定相同的输入,它们总是返回相同的输出。
在本章前面讨论的管道都是用纯函数实现的。 内置的 DatePipe
就是一个用纯函数实现的纯管道。ExponentialStrengthPipe
是如此, FlyingHeroesComponent
也是如此。 不久前你刚看过的 FlyingHeroesImpurePipe
就是一个用纯函数实现的非纯管道。
但是一个纯管道必须总是用纯函数实现。忽略这个警告将导致失败并带来一大堆这样的控制台错误:表达式在被检查后被变更。