每一个应用程序都以一个简单的任务开始:获取数据,转换它们,并将它们展现给用户。 获取数据能够像建立本地变量同样简单,也能够像经过WebSocket传输流数据同样复杂。javascript
一旦数据到达,您能够将其原始的toString值直接推送到视图中,但这不多能提供良好的用户体验。 例如,在大多数使用状况下,用户更喜欢以1988年4月15日这样的简单格式查看日期,而不是原始字符串格式Fri Apr 15 1988 00:00:00 GMT-0700(太平洋夏令时)。html
显然,一些值能够从一些编辑中受益。 您可能会注意到,您但愿在许多应用程序内部和许多应用程序中重复执行许多相同的转换。 你几乎能够把它们想象成风格。 事实上,您可能会喜欢将它们应用到HTML模板中,就像样式同样。java
介绍Angular管道,这是一种编写显示值转换的方法,您能够在HTML中声明这些转换。 尝试一下实例(查看源代码)。git
管道将数据做为输入并将其转换为所需的输出。 在此页面中,您将使用管道将组件的生日属性转换为人性化的日期。github
lib/src/hero_birthday1_component.dartweb
import 'package:angular/angular.dart'; @Component( selector: 'hero-birthday', template: "<p>The hero's birthday is {{ birthday | date }}</p>", pipes: const [COMMON_PIPES], ) class HeroBirthdayComponent { DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988 }
关注组件的模板。算法
<p>The hero's birthday is {{ birthday | date }}</p>
在插值表达式中,经过管道运算符(|)将组件的生日值传递给右侧的日期管道函数。 全部管道都是这样工做的。express
Date(日期)和Currency(货币)管道须要ECMAScript国际化API。 Safari和其余旧版浏览器不支持它。 您可使用polyfill添加支持。json
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>
Angular附带一系列管道,如DatePipe,UpperCasePipe,LowerCasePipe,CurrencyPipe,PercentPipe。它们均可用于任何模板。api
在API参考的管道主题中了解更多关于这些和许多其余内置管道的信息; 过滤包含单词“管道”的条目。
因为本页附录中解释了Angular没有FilterPipe或OrderByPipe的缘由。
管道能够接受任意数量的可选参数来微调其输出。 要向管道添加参数,请使用冒号(:)跟随管道名称,而后使用参数值(例如currency:"EUR")。 若是管道接受多个参数,请使用冒号分隔值(如slice:1:5)
修改生日模板以给日期管道一个格式参数。 格式化英雄的生往后,它呈现为04/15/88:
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
参数值能够是任何有效的模板表达式(请参阅模板语法页面的模板表达式部分),例如字符串文字或组件属性。 换句话说,您能够经过绑定来控制格式,就像您经过绑定控制生日值同样。
编写第二个组件,将管道的格式参数绑定到组件的format属性。 这是该组件的模板:
lib/src/hero_birthday2_component.dart (template)
template: ''' <p>The hero's birthday is {{ birthday | date:format }}</p> <button (click)="toggleFormat()">Toggle Format</button> ''',
您还向模板添加了一个按钮,并将其单击事件绑定到组件的toggleFormat()方法。 该方法在短格式("shortDate")和较长格式("fullDate")之间切换组件的format属性。
lib/src/hero_birthday2_component.dart (class)
class HeroBirthday2Component { DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988 bool toggle = true; get format => toggle ? 'shortDate' : 'fullDate'; void toggleFormat() { toggle = !toggle; } }
当您点击该按钮时,显示的日期在“04/15/1988”和“Friday, April 15, 1988”之间交替。
在Date Pipe API Reference页面阅读有关DatePipe格式选项的更多信息。
您能够将管道链接成可能有用的组合。 在如下示例中,要以大写形式显示生日,生日将连接到DatePipe并链接到UpperCasePipe。 生日显示为APR 15, 1988。
The chained hero's birthday is {{ birthday | date | uppercase}}
这个例子显示了FRIDAY, APRIL 15, 1988,它连接上面的相同管道,可是还传递了一个参数。
The chained hero's birthday is {{ birthday | date:'fullDate' | uppercase}}
您能够编写本身的自定义管道。 这是一个名为ExponentialStrengthPipe的自定义管道,能够提高英雄的力量:
lib/src/exponential_strength_pipe.dart
import 'dart:math' as math; import 'package:angular/angular.dart'; /* * Raise the value exponentially * Takes an exponent argument that defaults to 1. * Usage: * value | exponentialStrength:exponent * Example: * {{ 2 | exponentialStrength:10}} * formats to: 1024 */ @Pipe('exponentialStrength') class ExponentialStrengthPipe extends PipeTransform { num transform(num value, num exponent) => math.pow(value ?? 0, exponent ?? 1); }
这个管道定义揭示了如下关键点:
PipeTransform接口
transform方法对于管道是必不可少的。 PipeTransform接口定义该方法并指导工具和编译器。 从技术上讲,这是可选的; 不管角度如何,Angular都会查找并执行transform方法。
如今您须要一个组件来演示管道。
lib/src/power_booster_component.dart
import 'package:angular/angular.dart'; import 'exponential_strength_pipe.dart'; @Component( selector: 'power-booster', template: ''' <h2>Power Booster</h2> <p>Super power boost: {{2 | exponentialStrength: 10}}</p> ''', pipes: const [ExponentialStrengthPipe]) class PowerBoosterComponent {}
请注意如下几点:
记住管道列表
您必须手动注册自定义管道。 若是您不这样作,Angular会报告错误。 在前面的例子中,你没有列出DatePipe,由于全部的Angular内置管道都是预先注册的。
要在实例中查看行为(查看源代码),请更改模板中的值和可选的指数。
更新模板以测试自定义管道并非颇有趣。 将示例升级到“Power Boost Calculator”,它使用ngModel将您的管道和双向数据绑定相结合。
lib/src/power_boost_calculator_component.dart
import 'package:angular/angular.dart'; import 'package:angular_forms/angular_forms.dart'; import 'exponential_strength_pipe.dart'; @Component( selector: 'power-boost-calculator', template: ''' <h2>Power Boost Calculator</h2> <div>Normal power: <input type="number" [(ngModel)]="power"/></div> <div>Boost factor: <input type="number" [(ngModel)]="factor"/></div> <p> Super Hero Power: {{power | exponentialStrength: factor}} </p> ''', directives: const [CORE_DIRECTIVES, formDirectives], pipes: const [ExponentialStrengthPipe], ) class PowerBoostCalculatorComponent { num power = 5; num factor = 1; }
Angular经过在每一个DOM事件以后运行的更改检测过程查找数据绑定值的更改:每次击键,鼠标移动,计时器滴答和服务器响应。 这多是昂贵的。 Angular努力尽量下降成本并适当。
当您使用管道时,Angular会选择更简单,更快速的变动检测算法。
在下一个示例中,组件使用默认的积极变化检测策略来监控并更新其hero列表中每一个英雄的显示。 这是模板:
lib/src/flying_heroes_component.html (v1)
New hero: <input type="text" #box (keyup.enter)="addHero(box.value); box.value=''" placeholder="hero name"> <button (click)="reset()">Reset</button> <div *ngFor="let hero of heroes"> {{hero.name}} </div>
伴随组件类提供英雄,将英雄添加到列表中,并能够重置列表。
lib/src/flying_heroes_component.dart (v1)
class FlyingHeroesComponent { List<Hero> heroes; bool canFly = true; FlyingHeroesComponent() { reset(); } void addHero(String name) { name = name.trim(); if (name.isEmpty) return; var hero = new Hero(name, canFly); heroes.add(hero); } void reset() { heroes = new List<Hero>.from(mockHeroes); } }
你能够添加英雄和Angular更新显示。 若是你点击reset按钮,Angular用原有英雄的新列表替换heroes并更新显示。 若是您添加了删除或更改英雄的功能,Angular会检测这些更改并更新显示。
将一个FlyingHeroesPipe添加到*ngFor迭代器,该迭代器将英雄列表过滤到只能飞行的英雄。
lib/src/flying_heroes_component.html (flyers)
<div *ngFor="let hero of (heroes | flyingHeroes)"> {{hero.name}} </div>
这是FlyingHeroesPipe实现,它遵循前面描述的自定义管道模式。
lib/src/flying_heroes_pipe.dart (pure)
import 'package:angular/angular.dart'; import 'heroes.dart'; @Pipe('flyingHeroes') class FlyingHeroesPipe extends PipeTransform { List<Hero> transform(List<Hero> value) => value.where((hero) => hero.canFly).toList(); }
请注意实例中的奇怪行为(查看源代码):添加飞行英雄时,它们都不会显示在“飞翔的英雄”下。
虽然你没有获得你想要的行为,但Angular并无被破坏。 它只是使用不一样的变动检测算法,忽略对列表或其任何项目的更改。
注意如何添加一个英雄:
heroes.add(hero);
您将英雄添加到英雄列表中。 对列表的引用没有改变。 这是同一个列表。 这都是Angular关心的。 从它的角度来看,一样的列表,没有变化,没有显示更新。
为了解决这个问题,建立一个新的英雄列表并将其分配给heroes。 此次Angular检测到列表引用已经改变。 它执行管道并用新的列表更新显示,其中包括新的飞行英雄。
若是您更改列表,则不会调用管道,而且不会更新显示; 若是您替换列表,管道将执行并更新显示。 Flying Heroes应用程序经过复选框开关和附加显示扩展代码,以帮助您体验这些效果。
替换列表是发信号通知Angular更新显示的有效方式。 你何时更换清单? 数据发生变化时。 在这个例子中,这是一个简单的规则,其中更改数据的惟一方法是添加一个英雄。
更常见的状况是,您不知道数据什么时候发生变化,特别是在以多种方式变异数据的应用程序中,可能在远离应用程序的位置。 这样的应用程序中的组件一般没法了解这些更改。 此外,篡改组件设计以适应管道是不明智的。 努力保持组件类独立于HTML。 组件应该不知道管道。
为了过滤飞行英雄,请考虑一个不纯的管道。
有两类管道:纯净和不纯。 管道默认是纯净的。 到目前为止,你看到的每一个管道都是纯净的。 经过将pure设置为false,可使管道不纯。 你可让FlyingHeroesPipe不纯像这样:
@Pipe('flyingHeroes', pure: false)
在此以前,先了解纯净和不纯的区别,从纯净的管道开始。
仅当Angular检测到对输入值的纯粹更改时才执行纯管道。 在AngularDart中,纯粹的改变仅仅来自对象引用的改变(假设全部东西都是Dart中的对象)。
Angular忽略(复合)对象内的更改。 若是您更改输入月份,添加到输入列表或更新输入对象属性,它将不会调用纯管道。
这看起来颇有限制,但速度也很快。 对象引用检查的速度比深刻检查差别要快得多 - 因此Angular能够快速肯定它是否能够跳过管道执行和视图更新。
出于这个缘由,若是您能够接受变动检测策略,则最好使用纯净的管道。 当你不能时,你可使用不纯的管道。
或者你可能根本不使用管道。 用组件的属性来追求管道的目的可能会更好,这点在本页稍后会讨论。
Angular在每一个组件更改检测周期执行不纯管道。 常常调用不纯的管道,就像每次按键或鼠标移动同样。
考虑到这一点,谨慎使用不纯管道。 昂贵的,长期运行的管道可能会破坏用户体验。
翻转开关将FlyingHeroesPipe变成FlyingHeroesImpurePipe。 完整的实现以下:
lib/src/flying_heroes_pipe.dart (impure)
@Pipe('flyingHeroes', pure: false) class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
lib/src/flying_heroes_pipe.dart (pure)
import 'package:angular/angular.dart'; import 'heroes.dart'; @Pipe('flyingHeroes') class FlyingHeroesPipe extends PipeTransform { List<Hero> transform(List<Hero> value) => value.where((hero) => hero.canFly).toList(); }
您从FlyingHeroesPipe继承以证实内部没有任何变化。 惟一的区别是管道元数据中的纯标志。
对于不纯的管道来讲,这是一个很好的选择,由于转换函数很简单快捷。
List<Hero> transform(List<Hero> value) => value.where((hero) => hero.canFly).toList();
您能够从FlyingHeroesComponent派生FlyingHeroesImpureComponent。
lib/src/flying_heroes_component.dart (impure component)
@Component( selector: 'flying-heroes-impure', templateUrl: 'flying_heroes_component.html', pipes: const [FlyingHeroesImpurePipe], directives: const [CORE_DIRECTIVES, formDirectives], ) class FlyingHeroesImpureComponent extends FlyingHeroesComponent { FlyingHeroesImpureComponent() { title = 'Flying Heroes (impure pipe)'; } }
惟一的实质性变化是模板中的管道。 您能够在实例(查看源代码)中确认,当您添加英雄时,即便您变动heroes列表,飞行英雄也会显示更新。
Angular AsyncPipe是一个不纯管道的有趣例子。 AsyncPipe接受Future或Stream做为输入并自动订阅输入,最终返回发出的值。
AsyncPipe也是有状态的。 管道保持对输入Stream的订阅,并在到达时保持该Stream的值。
下一个示例使用异步管道将消息字符串(message)Stream绑定到视图。
lib/src/hero_async_message_component.dart
import 'dart:async'; import 'package:angular/angular.dart'; @Component( selector: 'hero-message', template: ''' <h2>Async Hero Message and AsyncPipe</h2> <p>Message: {{ message | async }}</p> <button (click)="resend()">Resend</button> ''', pipes: const [COMMON_PIPES], ) class HeroAsyncMessageComponent { static const _msgEventDelay = const Duration(milliseconds: 500); Stream<String> message; HeroAsyncMessageComponent() { resend(); } void resend() { message = new Stream.periodic(_msgEventDelay, (i) => _msgs[i]).take(_msgs.length); } List<String> _msgs = <String>[ 'You are my hero!', 'You are the best hero!', 'Will you be my hero?' ]; }
异步管道将样板文件保存在组件代码中。 该组件没必要订阅异步数据源,提取已解析的值并将其公开以进行绑定,而且必须在其销毁时取消订阅(内存泄漏的有效来源)。
再写一个不纯的管道,一个发出HTTP请求的管道。
请记住,每隔几毫秒就会调用不纯的管道。 若是你不注意,这个管道将用请求折腾服务器。
在如下代码中,管道只在请求URL发生更改和缓存服务器响应时调用服务器。
lib/src/fetch_json_pipe.dart
import 'dart:convert'; import 'dart:html'; import 'package:angular/angular.dart'; @Pipe('fetch', pure: false) class FetchJsonPipe extends PipeTransform { dynamic _cachedData; String _cachedUrl; dynamic transform(String url) { if (url != _cachedUrl) { _cachedUrl = url; _cachedData = null; HttpRequest.getString(url).then((s) { _cachedData = JSON.decode(s); }); } return _cachedData; } }
如今在一个线束组件中演示它,该组件的模板定义了对这个管道的两个绑定,都请求heroes.json文件中的heroes。
lib/src/hero_list_component.dart
import 'package:angular/angular.dart'; import 'fetch_json_pipe.dart'; @Component( selector: 'hero-list', template: ''' <h2>Heroes from JSON File</h2> <div *ngFor="let hero of ('heroes.json' | fetch) "> {{hero['name']}} </div> <p>Heroes as JSON: {{'heroes.json' | fetch | json}}</p> ''', directives: const [CORE_DIRECTIVES], pipes: const [COMMON_PIPES, FetchJsonPipe]) class HeroListComponent {}
该组件呈现以下:
管道的数据请求断点显示以下:
在前面的代码示例中,第二个提取管道绑定显示了更多的管道连接。 它经过连接到内置的JsonPipe以JSON格式显示相同的英雄数据。
使用JsonPipe进行调试:JsonPipe提供了一种简单的方法来诊断离奇失败的数据绑定,或者检查将来绑定的对象。
纯管道使用纯功能。 纯函数处理输入并返回值,但没有可检测到的反作用。 给定相同的输入,他们应该老是返回相同的输出。
本页前面讨论的管道是用纯函数实现的。 内置的DatePipe是一个纯函数实现的纯管道。 ExponentialStrengthPipe和FlyingHeroesPipe也是如此。 回过头来,你回顾了FlyingHeroesImpurePipe--一个纯粹功能的不纯管道。
老是要实现一个纯函数的纯管道。 不然,你会看到不少关于表达式被检查后改变的控制台错误。
管道是封装和共享常见显示值转换的好方法。 像样式同样使用它们,将它们放入模板表达式中,以丰富视图的吸引力和可用性。
在API参考中探索Angular的内置管道库。 尝试编写一个自定义管道,并可能将其贡献给社区。
Angular不提供过滤或排序列表的管道。 熟悉Angular 1的开发人员将这些知识视为filter和orderBy。 Angular中没有等价物。
这不是一个疏忽。 Angular不提供这样的管道,由于它们表现不佳,而且避免操控性变弱。 filter和orderBy都须要引用对象属性的参数。 在本页面的前面,您了解到这些管道必须是不纯的,而且Angular在几乎每一个变动检测周期都会调用不纯的管道。
过滤和特殊分类是昂贵的操做。 当Angular每秒钟屡次调用这些管道方法时,即便是中等大小的列表,用户体验也会严重降级。 filter和orderBy常常被滥用在Angular 1应用程序中,致使投诉Angular自己很慢。从间接的意义上说,Angular 1经过首先提供filter和orderBy来准备这个性能陷阱是公平的。
若是不那么明显,缩小危险也是使人信服的。 想象一下,排序管道应用于英雄列表。 该列表可能按如下方式按英雄name和planet属性排序:
<!-- NOT REAL CODE! --> <div *ngFor="let hero of heroes | orderBy:'name,planet'"></div>
您经过文本字符串来识别排序字段,指望管道经过索引引用属性值(如hero ["name"])。不幸的是,主动减小操纵Hero属性名称让Hero.name和Hero.planet成为Hero.a和Hero.b. 显然 hero[”name“] 不起做用。
虽然有些人可能并不在乎这种积极的态度,但Angular的产品不该该阻止任何人积极贬低。 所以,Angular团队决定Angular提供的全部内容都将安全地缩小。
Angular团队和许多经验丰富的Angular开发人员强烈建议将过滤和排序逻辑移植到组件自己中。 该组件能够公开一个filteredHeroes或sortedHeroes属性,并控制执行支持逻辑的时间和频率。 您能够在管道中放置并在应用程序中共享的任何功能均可以写入过滤/排序服务并注入到组件中。
若是这些性能和缩小比例考虑不适用于您,您能够随时建立本身的这种管道(相似于FlyingHeroesPipe)或在社区中找到它们。