JTangming / blog

My repository on GitHub.
Other
53 stars 0 forks source link

angular2 管道(pipes) #1

Open JTangming opened 8 years ago

JTangming commented 8 years ago

1.管道

我们的很多应用场景是基于这样一个简单的任务,获取数据,转换过滤后再展示给用户,angular2中我们引入了管道(Pipes)的概念,即管道是用来将数据模型中的数据经过过滤转换,然后通过模板内并展示给用户,这样做是为了更好的用户体验,例如从视图模型中直接获得的数据,不一定完全是我们想要的格式或者适合于人们查看的,举个例子,我们需要获取一个班级所有学生的平均分:

<div>this class‘s average is: {{getAvgScore()}}</div>

虽然通过视图模型中的getAvgScore方法获取到了我们需要的平均分来展示了,但是可能我们获取到的数据是一个除不断的多位小数位的数据,那么这样的结果看上去是不那么顺眼的。除了在视图模型的方法来控制小数位外,我们也可以利用管道来格式化这样的数据,也就是在模板里改变数据的显示格式。这就是angular2中管道的作用。

1.1 管道的用法

在angular2中,管道是在模板中对输入数据进行变换,并输出变换后的结果。在模板的内嵌表达式中,使用管道操作符“|”和其右边的管道方法来实现一个管道操作,使用“:”来向管道传入参数,所有的管道都是沿用这样这的一种机制。我们举个简单的例子来说明一下管道的使用:

import {Component} from 'angular2/core'
@Component({
    selector: 'hero-birthday',
    template: `<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }}</p>`
})
export class HeroBirthday {
    birthday = new Date(1988,3,15); // April 15, 1988
}

我们通过视图组件获取到我们要输出的生日日期birthday,通过插值和模板联系起来,在模板的内嵌表达式中,我们输出生日组件的值是通过管道操作符“|”和其右边的内置管道Data Pipe方法来实现的。至于管道的参数,我们在内置管道Data Pipe方法后边加冒号(:)来添加参数值,并且如果有多个参数的话,我们用多个冒号来区分开参数就好了。

最后要说一下的是,angular2管道可以链式运用。我们可以将多个管道通过“|”链式的书写到一个实用的组合体上,如我们将birthday链到DatePipe和UpperCasePipe上以便我们将生日日期显示为大写,下面的日期将会是APR 15, 1988:

<p>The chained hero's birthday is {{ birthday | date | uppercase}}</p>

1.2 管道的内置方法

为了方便使用,Angular2针对之前的经验,设置了一套常用的内置管道,如DatePipe,JsonPipe,UpperCasePipe, LowerCasePipe,CurrencyPipe,PercentPipe及SlicePipe.其目的是在任何模板中都可以便捷使用。我们将详细介绍他们的具体用法。 DatePipe是对日期\时间数据进行格式变换,在模板中直接使用date来引用DatePipe,参数用来指定所需的格式,需要说明的是,不需要再在视图组件中声明,

 <p>{{day | date: 'yyMMdd'}}</p>

JsonPipe是将Json数据对象转换成字符串格式输出,在模板中使用json来引用JsonPipe,其实现是基于JSON.stringify(),这个管道主要用来调试。

<p>{{key1: "value1", key2: "value2"}} | json</p>

UpperCasePipe&LowerCasePipe用于将输入的字符串转换成大小写,在模板中直接使用uppercase&lowercase即可。

<p>{{“this is a demo” | uppercase}} | json</p>

CurrencyPipe是将获取到的金钱数转换成特定格式的数字,在模板中直接使用currency来引用CurrencyPipe。

<p>{{price | currency: 'USD': true}}</p>

PercentPipe是将数值转换成百分比,在模板中使用percent来引用PercentPipe即可。

<p>{{1.23456 | percent: '1.2-3'}} | json</p>

例子中的“:'1.2-3'”表示调用这个这个管道时传入的参数为“'1.2-3'”,对于PercentPipe,这三个数字分别依次表示最少整数位、最少小数位和最多小数位。

SlicePipe是用来提取输入字符串中的指定切片,在模板中使用slice来引用SlicePipe。第一个参数指定切片的起始索引,第二个参数指定切片的终止索引的下一个。

<p>{{ '01234567890' | slice:1:4 }}</p>

2.自定义管道

通过上一节的内置管道可以看出,angular2内置的管道并不是特别的丰富,更进一步的是angular2允许自定义管道。自定义一个管道需要以下两个步骤:

2.1、声明元数据

和实现一个组件类似,一个自定义的管道也是具有特定元数据的类,如

import {Component,Pipe} from "angular2/core";
@Pipe({name: "anyPipeName"})
class anyPipeNamePipe {...}

Pipe注解为被装饰的类附加了管道元数据,其最重要的属性是name,也就是我们在模板中调用这个管道时使用的名称。上面定义的管道,我们可以在模板中这样使用:

<p>{{ data | anyPipeName }}</p>

2.2 实现transform方法

定义一个自定义的管道必须实现一个预定的方法transform(input,args),其中这个方法的input参数代表输入数据,args参数代表输入参数,返回值将被作为管道的输出。

import {Component,Pipe} from "angular2/core";
@Pipe({name: "anyPipeName"})
class anyPipeNamePipe {
    transform(input,args){
        return ...;
    }
}

通过以上定义一个自定义的管道,需要说明的是这样的一个pipe需要以下这样几个关键的点:

最后我们通过例子来说明自定义管道的使用,但是需要特别注意的是:

自定义管道完整的例子如下:

import {Component,Pipe} from "angular2/core";
import {bootstrap} from "angular2/platform/browser";

@Pipe({name:"title"})
class TitlePipe{
    transform(input,args){
        return input.split(" ")
                .map(word => word[0].toUpperCase() + word.slice(1))
                .join(" ");
    }
}

@Component({
    selector:"Demo-app",
    template:`  
        <p>{{text | title}}</p>
    `,
    pipes : [TitlePipe]
})
class DemoApp{
    constructor(){
        this.text = "what a wonderful world!";
    }
}

bootstrap(DemoApp);

3.管道状态

3.1 无状态的管道

无状态的管道是一个纯粹的方法,流入的数据将不会记录任何东西,或者不会导致任何的副作用。大多数的管道是无状态的,例如我们之前例子的Datapipe就是一个无状态的pipe。据我们之前了解到的管道,包括angular2内置的管道,都具有这么一个特点,就是其输出仅仅是依赖于输入,这就是angular2中的无状态管道,对于无状态的管道,当视图组件的输入没有变化时,angular2框架是不会重新计算管道的输出的。

3.2 有状态的管道

有状态的管道在概念上类似于面向对象编程类,它们可以管理数据的变换,例如管道创建一个HTTP请求,存储它的返回和显示结果,就是一个有状态的pipe。需要注意的是,检索或请求数据的管道应该要谨慎使用,因为使用网路数据往往会引入错误的条件,在javascript中处理更优于在模板中处理。我们可以为了特定的后端和基本的异常捕获而创建自定义的pipe来减轻任何风险。

angular2对有状态的管道定义的关键在于使用使用Pipe注解的属性“pure”,并设置该属性的值为false即可,其作用是要求angular2框架在每个变化检查周期都执行管道的transform()方法。下面我们给一个例子,实现一个10到0的倒数计时器。

import {Component,Pipe} from "angular2/core";
import {bootstrap} from "angular2/platform/browser";

@Pipe({
    name : "countdown",
    pure : false
})
class CountdownPipe {
    transform(input){
        this.initOnce(input);
        return this.counter;
    }
    initOnce(input){
        this.counter = input;
        this.timer = setInterval(() => {
            this.counter--;
            if(this.counter === 0) 
                clearInterval(this.timer);
        }, 1000);
    }
}
@Component({
    selector:"demo-app",
    template:`<h1>this is a stateful pipe : {{ 10 | countdown }}</h1>`,
    pipes : [CountdownPipe]
})
class DemoApp{} 
bootstrap(DemoApp);

从这个例子中可以看出,自定义管道countdownPipe的输出不仅依赖输出,还依赖与其内部的变化或者运行状态。

而angular2中,AsyncPipe是有状态管道的一个标志性的例子,AsyncPipe它的输入是一个异步对象:Promise对象、Observable对象或者EventEmitter对象,并且自动的订阅(subscrib)输入对象,最终每当异步对象产生新的值,AsyncPipe会返回这个新的值,它的有状态性是因为pipe维护一个输入的订阅并且它的返回值也依赖于这个订阅器。下面给出的例子,我们将用AsyncPipe绑定一个简单的promise给一个view。

import {Component} from 'angular2/core';
// Initial view: "Message: "
// After 500ms: Message: You are my Hero!"
@Component({
    selector: 'hero-message',
    template: 'Message: {{delayedMessage | async}}',
})
export class HeroAsyncMessageComponent {
    delayedMessage: Promise<string> = new Promise((resolve, reject) => {
    setTimeout(() => resolve('You are my Hero!'), 500);
    });
}

3.3 管道无状态和有状态的区别

管道的有状态和无状态的区别,关键在于是否是需要Angular2框架在输入不变的情况下依然持续地进行变化检测,而angular2的无状态的管道是依赖输入的,即同样的输入,总是产生同样的输出。举个例子,例如我们上面的管道,当我们输入一个默认的数字后,输出值依赖其内部的运行状态变化,而无状态的管道,例如一个加减乘除的管道,在Angular2中,它被视为无状态的,因为它的一次输入不会产生多次输出。