[Angular2] Pipe(管道)

管道接收数据输入并转换成期望的数据格式输出。例如,我们将组件的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] Pipe(管道)_第1张图片

Angular2提供了很多内建的管道函数,例如:DatePipeUpperCasePipeLowerCasePipeCurrencyPipePercentPipe

带参数的管道

管道函数可以接收任意数量的参数。

我们这样添加参数,在管道名称的后面添加冒号(:)和参数值,例如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的值在shortDatefullDate之间切换:

// 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`之间切换。

[Angular2] Pipe(管道)_第2张图片

管道链

我们可以将多个管道链接在一起,例如:

<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装饰器提供元数据。
  • @Pipe装饰器接收的对象有个name属性,它的值就是我们在模板表达式中使用的管道的名称,它必须是有效的JavaScript标识符。
  • pipe类实现PipeTransform接口的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 { }

自定义管道的使用方式跟内建管道一样。但是我们要在组件的@Componentpipes数组中列出我们的自定义管道。

另一个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;
}

[Angular2] Pipe(管道)_第3张图片

管道和变化检测

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会检测到数组引用发生了变化,它会执行管道操作并更新界面。

[Angular2] Pipe(管道)_第4张图片

Pure管道和Impure管道

管道分成两种,pure和impure。默认的是pure类型。

我们将pure标志赋值为false,可以声明管道为impure类型。

@Pipe({
  name: 'flyingHeroes',
  pure: false
})

Pure管道

Angular只会在检测到pure change后才执行pure管道。pure change指的是值的变化(String, Number, Boolean, Symbol)或对象引用的变化(Date, Array, Function, Object)。也就是说,Angular会忽略对象内部的变化。

Impure管道

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)';
}

[Angular2] Pipe(管道)_第5张图片

AsyncPipe

Angular内建的AsyncPipe就是一个impure管道。它接收一个PromiseObservable作为输入,自动订阅它,最终返回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 { }

JsonPipe

在上面的例子中,我们还用到了Angular内建的JsonPipe。它的作用是将输入转换成JSON字符串输出。

参考资料

Angular2官方文档

你可能感兴趣的:([Angular2] Pipe(管道))