管道接收数据输入并转换成期望的数据格式输出。例如,我们将组件的birthday
属性转换成更友好的显示方式。
// app/birthday-demo2.component.ts
import {Component} from 'angular2/core'
@Component({
selector: 'birthday-demo',
template: `<p>The hero's birthday is {{ birthday | date }}</p>`
})
export class BirthdayDemo {
birthday = new Date(1988,3,15); // April 15, 1988
}
在插值表达式中,我们将组件的birthday
值通过管道操作符(|
)传递给DatePipe
函数。
Angular2提供了很多内建的管道函数,例如:DatePipe
、UpperCasePipe
、LowerCasePipe
、CurrencyPipe
、PercentPipe
。
管道函数可以接收任意数量的参数。
我们这样添加参数,在管道名称的后面添加冒号(:
)和参数值,例如currency:'EUR'
。如果是多个参数,参数值之间也用冒号隔开,例如slice:1:5
。
例如,将日期April 15, 1988
显示成04/15/88
:
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
参数值可以是任意有效的模板表达式。例如字符串字面量或者模板属性。也就是说,我们可以通过绑定来控制参数。
我们给组件添加一个format
属性和toggleFormat
方法,toggleFormat
方法将format
的值在shortDate
和fullDate
之间切换:
// app/birthday-demo2.component.ts
import {Component} from 'angular2/core'
@Component({
selector: 'birthday-demo',
template: `
<p>The hero's birthday is {{ birthday | date:format }}</p>
<button (click)="toggleFormat()">Toggle Format</button>
`
})
export class HeroBirthday2 {
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; }
}
当我们点击按钮时,显示的日期会在04/15/1988
和Friday, April 15, 1988`之间切换。
我们可以将多个管道链接在一起,例如:
<p>The chained hero's birthday is {{ birthday | date | uppercase}}</p>
如果管道有参数,我们必须要使用小括号来帮助模板编译器确定计算顺序。
<p>The chained hero's birthday is {{ birthday | date:'fullDate' | uppercase}}</p>
上面的例子显示的结果将是FRIDAY, APRIL 15, 1988
。我们可以使用小括号来让顺序看得更清晰一些。
<p>The chained hero's birthday is {{ ( birthday | date:'fullDate' ) | uppercase}}</p>
我们编写一个自定义管道来计算一个数的次幂。
// app/exponential-strength.pipe.ts
import {Pipe, PipeTransform} from 'angular2/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 {
var exp = parseFloat(exponent);
return Math.pow(value, isNaN(exp) ? 1 : exp);
}
}
管道的定义涉及几个方面:
@Pipe
装饰器提供元数据。@Pipe
装饰器接收的对象有个name
属性,它的值就是我们在模板表达式中使用的管道的名称,它必须是有效的JavaScript标识符。transform
方法。该方法接收一个输入值和一个可选的参数数组,返回转换后的值。我们编写 一个组件来演示自定义管道的使用:
// /app/power-boost-calculator.component.ts
import {Component} from 'angular2/core';
import {ExponentialStrengthPipe} from './exponential-strength.pipe';
@Component({
selector: 'power-booster',
template: `
<h2>Power Booster</h2>
<p>
Super power boost: {{2 | exponentialStrength: 10}}
</p>
`,
pipes: [ExponentialStrengthPipe]
})
export class PowerBooster { }
自定义管道的使用方式跟内建管道一样。但是我们要在组件的@Component
的pipes
数组中列出我们的自定义管道。
另一个demo,管道跟数据绑定同时使用:
// app/power-boost-calculator.component.ts
import {Component} from 'angular2/core';
import {ExponentialStrengthPipe} from './exponential-strength.pipe';
@Component({
selector: '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>
`,
pipes: [ExponentialStrengthPipe]
})
export class PowerBoostCalculator {
power = 5;
factor = 1;
}
Angular在每次JavaScrpt事件(按键、鼠标移动、定时器触发、服务器响应)之后会执行变化检测。如果我们使用管道,Angular会选择使用更简单更快速的变化检测算法。
// app/flying-heroes.component.ts
import {Component} from 'angular2/core';
import {FlyingHeroesPipe,
FlyingHeroesImpurePipe} from './flying-heroes.pipe';
import {HEROES} from './heroes';
@Component({
selector: 'flying-heroes',
template: `
<h2>{{title}}</h2>
<p>
New hero:
<input type="text" #box
(keyup.enter)="addHero(box.value); box.value=''"
placeholder="hero name">
<input id="can-fly" type="checkbox" [(ngModel)]="canFly"> can fly
</p>
<p>
<input id="mutate" type="checkbox" [(ngModel)]="mutate">Mutate array
<button (click)="reset()">Reset</button>
</p>
<h4>Heroes who fly (piped)</h4>
<div id="flyers">
<div *ngFor="#hero of (heroes | flyingHeroes)">
{{hero.name}}
</div>
</div>
<h4>All Heroes (no pipe)</h4>
<div id="all">
<div *ngFor="#hero of heroes">
{{hero.name}}
</div>
</div>
`,
styles: ['#flyers, #all {font-style: italic}'],
pipes: [FlyingHeroesPipe]
})
export class FlyingHeroesComponent {
heroes:any[] = [];
canFly = true;
mutate = true;
title = 'Flying Heroes (pure pipe)';
constructor() { this.reset(); }
addHero(name:string) {
name = name.trim();
if (!name) { return; }
let hero = {name, canFly: this.canFly};
if (this.mutate) {
// Pure pipe won't update display because heroes array reference is unchanged
// Impure pipe will display
this.heroes.push(hero);
} else {
// Pipe updates display because heroes array is a new object
this.heroes = this.heroes.concat(hero);
}
}
reset() { this.heroes = HEROES.slice(); }
}
////// impure pipe
@Component({
selector: 'flying-heroes-impure',
templateUrl: 'app/flying-heroes.component.html',
styles: ['.flyers, .all {font-style: italic}'],
pipes: [FlyingHeroesImpurePipe]
})
export class FlyingHeroesImpureComponent extends FlyingHeroesComponent {
title = 'Flying Heroes (impure pipe)';
}
// app/flying-heroes.pipe.ts
import {Flyer} from './heroes';
import {Pipe, PipeTransform} from 'angular2/core';
@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
transform(allHeroes:Flyer[]) {
return allHeroes.filter(hero => hero.canFly);
}
}
/////// impure pipe
@Pipe({
name: 'flyingHeroes',
pure: false
})
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
如果我们使用this.heroes.push(hero);
的方式向数组中添加数据,那么heroes
数组的对象引用没有变化,Angular认为这个数组没有变化,不会更新界面。如果使用concat
会创建一个新数组并赋值给heroes
,Angular会检测到数组引用发生了变化,它会执行管道操作并更新界面。
管道分成两种,pure和impure。默认的是pure类型。
我们将pure标志赋值为false,可以声明管道为impure类型。
@Pipe({
name: 'flyingHeroes',
pure: false
})
Angular只会在检测到pure change后才执行pure管道。pure change指的是值的变化(String, Number, Boolean, Symbol
)或对象引用的变化(Date, Array, Function, Object
)。也就是说,Angular会忽略对象内部的变化。
Angular会在每次组件变化检测周期执行impure管道。每次按键或者鼠标移动都会出发impure管道的执行。
我们将FlyingHeroesPipe
转换成FlyingHeroesImpurePipe
:
// app/flying-heroes.pipe.ts (partial)
@Pipe({
name: 'flyingHeroes',
pure: false
})
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
// app/flying-heroes.component.ts (partial)
@Component({
selector: 'flying-heroes-impure',
templateUrl: 'app/flying-heroes.component.html',
pipes: [FlyingHeroesImpurePipe]
})
export class FlyingHeroesImpureComponent extends FlyingHeroesComponent {
title = 'Flying Heroes (impure pipe)';
}
Angular内建的AsyncPipe就是一个impure管道。它接收一个Promise
或Observable
作为输入,自动订阅它,最终返回emitted value。它同时是有状态的。该管道为维护对输入的Observable
的订阅并保持从Observable
传递值。
// app/hero-async-message.component.ts
import {Component} from 'angular2/core';
import {Observable} from 'rxjs/Rx';
// Initial view: "Message: "
// After 500ms: Message: You are my Hero!"
@Component({
selector: '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>;
constructor() { this.resend(); }
resend() {
this.message$ = Observable.interval(500)
.map(i => this.messages[i])
.take(this.messages.length);
}
private messages = [
'You are my hero!',
'You are the best hero!',
'Will you be my hero?'
];
}
我们编写一个impure类型的缓存管道。它将请求的URL和结果缓存起来了,只会在URL变化时才发起服务器请求。
// pp/fetch-json.pipe.ts
import {Pipe, PipeTransform} from 'angular2/core';
import {Http} from 'angular2/http';
@Pipe({
name: 'fetch',
pure: false
})
export class FetchJsonPipe implements PipeTransform{
private fetched:any = null;
private prevUrl = '';
constructor(private _http: Http) { }
transform(url:string):any {
if (url !== this.prevUrl) {
this.prevUrl = url;
this.fetched = null;
this._http.get(url)
.map( result => result.json() )
.subscribe( result => this.fetched = result )
}
return this.fetched;
}
}
// app/hero-list.component.ts
import {Component} from 'angular2/core';
import {FetchJsonPipe} from './fetch-json.pipe';
@Component({
selector: 'hero-list',
template: `
<h2>Heroes from JSON File</h2>
<div *ngFor="#hero of ('heroes.json' | fetch) ">
{{hero.name}}
</div>
<p>Heroes as JSON:
{{'heroes.json' | fetch | json}}
</p>
`,
pipes: [FetchJsonPipe]
})
export class HeroListComponent { }
在上面的例子中,我们还用到了Angular内建的JsonPipe。它的作用是将输入转换成JSON字符串输出。
参考资料
Angular2官方文档