5.揭秘angular2学习 ------- 管道

管道简介

管道是什么?

Angular的管道可以看作成是一个数据格式化展示的工具。管道可以将数据格式化显示,而不改变源数据。比如关于日期的展示,对于源数据使用管道1可以以yyyy/MM/dd来展示,也可以使用管道2展示成Feb 28, 2017的形式。但原数据依然是date,并没有改变。利用管道我们可以将数据格式化的内容剥离出来,使之独立,有需要格式化展示的时候选择相应的管道进行处理即可。

如何使用

在Angular中需要使用管道操作符“|”来使用管道。形式如:

{{ 输入数据 | 管道 : 管道参数 }}
template: `
    

My birthday is {{ birthday | date: "MM/dd/y" }} ` ... export class PipeDemoComponent { birthday = new Date(1993, 3, 15); }

链式管道

管道可以像jQuery一样链式使用。

{{ data | Pipe1 | Pipe2 | Pipe3 | ... }}

表达式从左至右求值,date先通过Pipe1处理后将处理后的数据作为输入数据再进行Pipe2的处理,以此类推,直到所有管道都已执行完毕。

这种链式调用的方式可以展示更丰富更复杂的数据。

Angular内置管道

先说个题外话,学习到这里也总结出了一点Angular的规律,不知道各位看官发现没有:Angular2框架虽然看起来比较庞大,但是其支持的内容全是全面。比如我们现在学的管道与之前学的表单,以及将来要学习的指令等,Angular都会内置一些常用的,然后还支持自定义扩展,我很喜欢这种方式。题外话完毕。

Angualr封装了一些常用的内置管道。内置管道可以直接在任何模版表达式中使用,不需要通过import导入和在模块中声明。

Angular内置管道整理如图:

管道 类型 功能
DatePipe 纯管道 日期管道,格式化日期
JsonPipe 非纯管道 将输入数据对象经过JSON.stringify()方法转换后输出对象的字符串
UpperCasePipe 纯管道 将文本所有小写字母转换成大写字母
LowerCasePipe 纯管道 将文本所有大写字母转换成小写字母
DecimalPipe 纯管道 将数值按特定的格式显示文本
CurrentcyPipe 纯管道 将数值转百分比格式
SlicePipe 非纯管道 将数组或者字符串裁剪成新子集

下面会分别讲解每一个管道的用法。

DatePipe

关键词:日期
DatePipe用来格式化日期数据,使用方法如下:

expression | date: format
  1. expression可以是
    • Date日期对象
    • 日期字符串,如”2016/04/05”
    • 毫秒级时间戳
  2. format自定义日期格式如下表(取2016-06-08 20:05:08时间为例)
日期 标志符 缩写 全称 单标志符 双标志符
地区 G G(AD) GGGG(Anno Domini) - -
y - - y(2016) yy(16)
M MMM(Jun) MMMM(June) M(6) MM(06)
d - - d(8) dd(08)
星期 E EEE(Fri) EEEE(Friday) - -
时间(AM,PM) j - - j(8 PM) jj(08 PM)
12小时制时间 h - - h(8) hh(08)
24小时制时间 H - - H(20) HH(20)
m - - m(5) mm(05)
s - - s(8) ss(08)
时区 Z - Z(china Standard Time) - -
时区 z z(GMT-8:00) - - -

下面看例子:

@Component({
    selector: 'pipe-demo-date',
    template: `
        

{{date|date: "y-MM-dd EEEE"}}

` }) export class PipeDemoDateComponent { date:Date = new Date('2016-06-08 20:05:08'); }

输出结果为

2016-06-08 Wednesday

JsonPipe

关键词:Json
JsonPipe管道通过JSON.stringify()来将输入数据对象转换成对象字符串,该管道主要用于开发调试:

@Component({
    selector: 'pipe-demo-json',
    template: `
        
{{jsonObject | json}}</pre>
    `
})
export class PipeDemJsonComponent {
    jsonObject: Object = {foo: 'bar', baz: 'qux', nested: {xyz: 3, numbers: [1, 2]}};
}

输出结果为:

//json
{
    "foo": "bar",
    "baz": "qux",
    "nested": {
        "xyz": 3,
        "numbers": [
            1,
            2
        ]
    }
}

UpperCasePipe

UpperCasePipe管道用于将文本中所有小写字母转换成大写字母。
语法格式:

expression | uppercase

LowerCasePipe

LowerCasePipe管道用于将文本中所有大写字母转换成小写字母。
语法格式:

expression | lowercase

DecimalPipe

关键词:数值位数
DecimalPipe管道用于对数值的整数与小数部分按照指定规则进行格式化,这种格式化方式也成为本地格式化处理,语法如下:

expression | number[:digitInfo]

参数digitInfo的格式如下:

{minIntegerDigits}.{minFractionDigits}-{maxfractionDigits}
  • minIntegerDigits:整数部分保留最小的位数,默认值为1.
  • minFractionDigits:小数部分保留最小的位数,默认值为0.
  • maxFractionDigits:小数部分保留最大的位数,默认值为3.

用法如下:

@Component({
    selector: 'pipe-demo-number',
    template: `
        <div>
            <p>A 变量:{{a | number: '3.4-5'}}p>
            <p>B 变量:{{b | number: '3.1-5'}}p>
        div>
    `
})
export class PipeDemoNumberComponent {
    a: number = 2.718281828459045;
    b: number = 33456;
}

转换后结果:

a 变量:002.71828
b 变量:33,456.0

格式化变量a所采用的参数为3.4-5,参数.左边的3表示整数位最少保留三位,原值整数位为1位不足3位,所以用0补齐。参数.右边的4-5表示保留小数的最小数位为4为,最大数位为5位,因原始数据小数位大于5位,所以保留四舍五入后的5位小数。

格式化变量b所采用的参数为3.1-5,参数.左边的3表示整数位最少保留三维,原值正式位为5位,大于3位,所以全部保留。参数.右边的1-5表示保留小数的最小数位为1位,最大数位为5位,因原始数据没有小数位,因此采用最小1位限制的规则,小数位补0。

CurrencyPipe

关键词:货币
CurrentPipe管道可以将数值进行货币格式化处理。语法如下:

expression | currency[: currencyCode[: symbolDisplay[: digitInfo]]]

- 参数currentcyCode:表示要格式化的目标货币格式,值为ISO 4217货币码,如CNY人民币、USD美元、EUR欧元等。
- 参数symbolDisplay:表示以该类型货币的哪种格式显示,值为布尔值,true表示显示货币符号如¥、$等,false表示显示ISO 4217货币码如CNY、USD等。
- digitInfo:参考DecimalPipe管道。

来个例子:

@Component({
    selector: 'pipe-demo-currency',
    template: `
        <p>A 变量:{{ a | currency: 'USD': false }}p>
        <p>B 变量:{{ b | currency: 'CNY': true: '3.1-3'}}p>
    `
})
export class PipeDemoCurrencyComponent {
    a: number = 0.259;
    b: number = 20.6745;
}

转换后结果

A 变量:USD0.259
B 变量:¥020.675

PercentPipe

关键词:百分数
PercentPipe管道可以对数值进行百分比处理。语法如下:

expression | percent[: digitInfo]
  • 参数digitInfo:参考DecimalPipe管道

直接看例子:

@Component({
    selector: 'pipe-demo-Percent',
    template: `
        <div>
            <p>A 变量:{{ a | percent }}p>
            <p>B 变量:{{ b | percent: '3.1-3' }}p>
        div>
    `
})
export class PipeDemoPercentComponent {
    a: number = 0.259;
    b: number = 0.657;
}

输出:

A 变量:25.9%
B 变量:065.7%

注意

这里的百分化处理是在原值的基础上进行乘以100%,而不是简单的字符串拼接。

SlicePipe

关键词:截断
SlicePipe管道用于裁剪数组字符串,返回裁剪后的目标子集。语法如下:

expression | slice: start[: end]

SlicePipe是基于Array.prototype.slice()方法和String.prototype.slice()方法来实现的。

自定义管道

老规矩,Angular给了一些内置管道,那么肯定还可以自定义管道来解决特定的问题。下面就来学习如何自定义管道。

需求:将male展示为男,female展示为女

1.创建自定义管道

import { Pipe, PipeTransform } form '@angular/core';  //引入PipeTransform是为了继承transform方法

@Pipe({ name: 'sexReform' })  //name属性值惯用小驼峰是写法
export class SexReformPipe implements PipeTransform {
    transform(value: string): string {
        switch(value){
            case 'male': return '男';
            case 'female': return '女';
            default: return '不男不女或雌雄同体';
        } 
    }
}

2.使用自定义管道

//...
<p> {{ sexValue | sexReform }}p>
<p class="{{ sexValue | sexReform }}"</P>   //将返回值赋值给class
//...

3.注意:管道返回值不能返回html文档,会被转移展示!!不要用这种方法来创建节点。

管道的变化监测

在之前的数据绑定中,学习到Angular的变化监测机制,如果频繁的触发变化监测会引起性能问题。
但我们可以通过使用管道,让Angular选择使用更简单、更快速的变化监测策略来提高性能。
下面使用管道实现一个过滤联系人列表功能的例子来了解Angular管道的性能优化:

//...
@Pipe({ name: selectContanct })
export class SelectContanctPipe implements PipeTransform{
    transform(allContacts: Array, prefix: string){
        return allContacts.filter(contact => contact.name.match("^"+prefix));
    }
}
@Component({
    selector: 'pipe-demo',
    template: `
        type="text" #box (keyup.enter)="addContact(box.value); box.value=''" placeholder="输入联系人后回车添加" />
        
"let contact of (contacts | selectContactPipe: '李')"> {{contact.name}}
` }) export class PipeDemoComponent { contacts = [{name: '张三'}, {name: '李四'}]; addContact(name: string) { this.contacts.push({name}); } }

上面的管道过滤只显示”李”姓的联系人,如果用户输入一个”李”姓联系人,然后回车将该联系人添加至contacts数组中。然后我们会觉得联系人列表应该会实时显示新的联系人,然而并没有
这是因为Angular优化了管道的监测机制,它会忽略对象内值的变化,只会监测指向对象的指针是否发生改变。

这种管道称为纯管道,虽然纯管道优化了性能,但有时无法满足需求,就像上面的例子那样。
这时我们就需要另外一种变化监测机制,也就是非纯管道

Angular管道有两种变化监测机制,一种是纯管道(默认),另一种即非纯管道。

纯管道 (Pure Pipe)

  1. 使用纯管道时,只有监测到输入值发生纯变更才会调用纯管道的transform方法来转换数据。
  2. 纯变更是指对基本数据类型输入值的变更,或对对象的引用放生改变。

非纯管道 (Impure Pipe)

使用非纯管道,Angular组件在每个变化监测周期都会调用非纯管道。
1.设置非纯管道

@Pipe({ 
    name: 'selectContact',
    pure: false    //设置非纯管道
})

扩展阅读

安全导航操作符

Angular的模版表达式在某些特定场景中允许使用一些特殊的连接操作符,如本章介绍的管道操作符。
下面来学习安全导航操作符”?.”。
假设模版表达式detail.telNum的值为0123456789,下面的代码正常运行后界面将会显示为0123456789,代码如下:

<p>{{ detail.telNum }}p>

如果模版变量detail没被赋值,在Angular会报错,程序无法运行。
我们可以使用以下处理方式规避这种错误:

<p>{{ detail && detail.telNum }}p>  //疑问,这里的结果会是什么?

上述方法可以达到预期,但是遇到长属性路径时会显得臃肿。这时我们可以使用”?.”安全导航操作符来处理,避免因为属性路径中出现null或者undefined值而出现的错误。

<p>{{ detail?.telNum }}p>

双向绑定的原理

在模版部分的学习中,我们已经了解到,[(ngModel)]可以拆分为ngModel和ngModelChange两部分。其中,ngModel指令的输入属性用来设置元素的值,ngModelChange作为NgModel指令的输出属性用来监听元素值是否变化。
需要注意的是,ngModelChange属性并不会生成DOM事件,实际上它是一个EventEmitter类型的对象,[(ngModel)]的具体实现如下:

@Direvtive({
    selector: '[ngModel]',
    host: {
            "[value]": "ngModel",
            "(input)": "ngModelChange.next($event.target.value)"
    }
})
class NgModelDirective {
    @Input() ngModel: any;
    @Output() ngModelChange: EventEmitter = new EventEmitter();
}

host属性用来描述和指令元素相关的输入输出属性变化,即当[ngModel]的ngModelChange事件发生时就会触发input事件,当[ngModel]的ngModel值变化时就会更新value属性。

Angular提供了一种双向数据绑定的语法,即[(x)]。也就是说当Angular解析一个[(x)]的绑定目标时,相当于为这个x指令绑定一个名为x的输入属性和一个名为xChange的输出属性,示例代码如下:

<span [(x)]="e">span>
//等同于下面的代码
<span [x]="e" (xChange)="e=$event">span>

总的来说,双向数据绑定实际上就是通过输入属性存储数,同时通过一个与之对应的输出属性(输入属性+Change后缀)监听输入属性的数据变化来触发相应的事件。

了解Angular双向数据绑定的原理后,接下来通过创建一个支持双向绑定的组件例子来加深理解。该例子绑定一个Number的输入输出属性,同时在组件需要定义一个@Output输出属性来匹配@Input输入属性,示例代码如下:

//amount.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'amount',
    template: `
        
            子组件当前值: {{value}} -
            
        
    `
})
export class AmountComponent {
    @Input() value: number = 0;
    @Output() valueChange: EventEmitter = new EventEmitter();

    increment(){
        this.value++;
        this.valueChange.emit(this.value);
    }
}
//app.component.ts
import { Component, Input } from '@angular/core';
import { AmountComponent } from './amount.component';

@Component({
    selector: 'app',
    template: `
        <div>
            <div>
                <span>Number 1:span>
                <amount [(value)]="number1">amount>

            div>
            <div>
                <span>Number 2:span>
                <amount [(value)]="number2" (valueChange)="number2=$event">amount>
            div>
            <ul>
                <li>Number 1:父组件当前值:{{number1}}li>
                <li>Number 2:父组件当前值:{{number2}}li>
            ul>
        div>
    `
})
export class Parent {
    number1: number = 0;
    number2: number = 2;
}

你可能感兴趣的:(angular2)