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的规律,不知道各位看官发现没有:Angular2框架虽然看起来比较庞大,但是其支持的内容全是全面。比如我们现在学的管道与之前学的表单,以及将来要学习的指令等,Angular都会内置一些常用的,然后还支持自定义扩展,我很喜欢这种方式。题外话完毕。
Angualr封装了一些常用的内置管道。内置管道可以直接在任何模版表达式中使用,不需要通过import导入和在模块中声明。
Angular内置管道整理如图:
管道 | 类型 | 功能 |
---|---|---|
DatePipe | 纯管道 | 日期管道,格式化日期 |
JsonPipe | 非纯管道 | 将输入数据对象经过JSON.stringify()方法转换后输出对象的字符串 |
UpperCasePipe | 纯管道 | 将文本所有小写字母转换成大写字母 |
LowerCasePipe | 纯管道 | 将文本所有大写字母转换成小写字母 |
DecimalPipe | 纯管道 | 将数值按特定的格式显示文本 |
CurrentcyPipe | 纯管道 | 将数值转百分比格式 |
SlicePipe | 非纯管道 | 将数组或者字符串裁剪成新子集 |
下面会分别讲解每一个管道的用法。
关键词:日期
DatePipe用来格式化日期数据,使用方法如下:
expression | date: format
日期 | 标志符 | 缩写 | 全称 | 单标志符 | 双标志符 |
---|---|---|---|---|---|
地区 | 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
关键词: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管道用于将文本中所有小写字母转换成大写字母。
语法格式:
expression | uppercase
LowerCasePipe管道用于将文本中所有大写字母转换成小写字母。
语法格式:
expression | lowercase
关键词:数值位数
DecimalPipe管道用于对数值的整数与小数部分按照指定规则进行格式化,这种格式化方式也成为本地格式化处理,语法如下:
expression | number[:digitInfo]
参数digitInfo的格式如下:
{minIntegerDigits}.{minFractionDigits}-{maxfractionDigits}
用法如下:
@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。
关键词:货币
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管道可以对数值进行百分比处理。语法如下:
expression | percent[: digitInfo]
直接看例子:
@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管道用于裁剪数组或字符串,返回裁剪后的目标子集。语法如下:
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管道有两种变化监测机制,一种是纯管道(默认),另一种即非纯管道。
使用非纯管道,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;
}