779 lines
40 KiB
Plaintext
779 lines
40 KiB
Plaintext
block includes
|
||
include ../_util-fns
|
||
|
||
:marked
|
||
Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
|
||
Getting data could be as simple as creating a local variable or as complex as streaming data over a Websocket.
|
||
|
||
每个应用开始的时候差不多都是一些简单任务:获取数据、转换它们,然后把它们显示给用户。
|
||
获取数据可能简单到创建一个局部变量就行,也可能复杂到从WebSocket中获取数据流。
|
||
|
||
Once data arrive, we could push their raw `toString` values directly to the view.
|
||
That rarely makes for a good user experience.
|
||
E.g., almost everyone prefers a simple birthday date like
|
||
<samp>April 15, 1988</samp> to the original raw string format
|
||
— <samp>Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</samp>.
|
||
|
||
一旦取到数据,我们可以把它们原始值的`toString`结果直接推入视图中。
|
||
但这种做法很少能具备良好的用户体验。
|
||
比如,几乎每个人都更喜欢简单的日期格式,例如<samp>1988-04-15</samp>,而不是服务端传过来的原始字符串格式 —— <samp>Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</samp>。
|
||
|
||
Clearly some values benefit from a bit of massage. We soon discover that we
|
||
desire many of the same transformations repeatedly, both within and across many applications.
|
||
We almost think of them as styles.
|
||
In fact, we'd like to apply them in our HTML templates as we do styles.
|
||
|
||
显然,有些值最好显示成用户友好的格式。我们很快就会发现,在很多不同的应用中,都在重复做出某些相同的变换。
|
||
我们几乎会把它们看做某种CSS样式,事实上,我们也确实更喜欢在HTML模板中应用它们 —— 就像CSS样式一样。
|
||
|
||
Introducing Angular pipes, a way to write display-value transformations that we can declare in our HTML!
|
||
Try the <live-example></live-example>.
|
||
|
||
欢迎来到Angular“管道(Pipe)”的世界!我们可以把这种简单的“值-显示”转换器声明在HTML中。
|
||
试试<live-example></live-example>。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Using Pipes
|
||
## 使用管道
|
||
|
||
A pipe takes in data as input and transforms it to a desired output.
|
||
We'll illustrate by transforming a component's birthday property into
|
||
a human-friendly date.
|
||
|
||
管道把数据作为输入,然后转换它,给出期望的输出。
|
||
我们将把组件的`birthday`属性转换成对人类更友好的日期格式,来说明这一点:
|
||
|
||
+makeExample('pipes/ts/app/hero-birthday1.component.ts', null, 'app/hero-birthday1.component.ts')(format='.')
|
||
|
||
:marked
|
||
Focus on the component's template.
|
||
|
||
重点看下组件的模板。
|
||
|
||
+makeExample('pipes/ts/app/app.component.html', 'hero-birthday-template')(format=".")
|
||
|
||
:marked
|
||
Inside the interpolation expression we flow the component's `birthday` value through the
|
||
[pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/index/DatePipe-class.html)
|
||
function on the right. All pipes work this way.
|
||
|
||
在这个插值表达式中,我们让组件的`birthday`值通过[管道操作符](./template-syntax.html#pipe)( | )流动到
|
||
右侧的[Date管道](../api/common/index/DatePipe-class.html)函数中。所有管道都会用这种方式工作。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The `Date` and `Currency` pipes need the **ECMAScript Internationalization API**.
|
||
Safari and other older browsers don't support it. We can add support with a polyfill.
|
||
|
||
`Date`和`Currency`管道需要**ECMAScript国际化(I18n)API**,但Safari和其它老式浏览器不支持它,该问题可以用垫片(Polyfill)解决。
|
||
|
||
code-example(language="html").
|
||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Built-in pipes
|
||
## 内建的管道
|
||
Angular comes with a stock of pipes such as
|
||
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
|
||
They are all immediately available for use in any template.
|
||
|
||
Angular内置了一些管道,比如`DatePipe`、`UpperCasePipe`、`LowerCasePipe`、`CurrencyPipe`和`PercentPipe`。
|
||
它们全都可以直接用在任何模板中。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Learn more about these and many other built-in pipes in the [API Reference](../api/#!?apiFilter=pipe);
|
||
filter for entries that include the word "pipe".
|
||
|
||
要学习更多内建管道的知识,参见[API参考手册](../api/#!?apiFilter=pipe),并用“pipe”为关键词对结果进行过滤。
|
||
|
||
Angular 2 doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in an [appendix below](#no-filter-pipe).
|
||
|
||
Angular 2没有`FilterPipe`或`OrderByPipe`管道,原因在[后面的附录中](#no-filter-pipe)有解释。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Parameterizing a Pipe
|
||
## 对管道进行参数化
|
||
|
||
A pipe may accept any number of optional parameters to fine-tune its output.
|
||
We add parameters to a pipe by following the pipe name with a colon ( : ) and then the parameter value
|
||
(e.g., `currency:'EUR'`). If our pipe accepts multiple parameters, we separate the values with colons (e.g. `slice:1:5`)
|
||
|
||
管道可能接受任何数量的可选参数来对它的输出进行微调。
|
||
我们可以在管道名后面添加一个冒号( : )再跟一个参数值,来为管道添加参数(比如`currency:'EUR'`)。
|
||
如果我们的管道可以接受多个参数,那么就用冒号来分隔这些参数值(比如`slice:1:5`)。
|
||
|
||
We'll modify our birthday template to give the date pipe a format parameter.
|
||
After formatting the hero's April 15th birthday, it should render as **<samp>04/15/88</samp>**:
|
||
|
||
我们将通过修改生日模板来给这个日期管道提供一个格式化参数。
|
||
当格式化完该英雄的4月15日生日之后,它应该被渲染成**<samp>04/15/88</samp>**。
|
||
|
||
+makeExample('pipes/ts/app/app.component.html', 'format-birthday')(format=".")
|
||
|
||
:marked
|
||
The parameter value can be any valid
|
||
[template expression](./template-syntax.html#template-expressions)
|
||
such as a string literal or a component property.
|
||
In other words, we can control the format through a binding the same way we control the birthday value through a binding.
|
||
|
||
参数值可以是任何有效的[模板表达式](./template-syntax.html#template-expressions),比如字符串字面量或组件的属性。
|
||
换句话说,借助属性绑定,我们也可以像用绑定来控制生日的值一样,控制生日的显示格式。
|
||
|
||
Let's write a second component that *binds* the pipe's format parameter
|
||
to the component's `format` property. Here's the template for that component:
|
||
|
||
我们来写第二个组件,它把管道的格式参数*绑定*到该组件的`format`属性。这里是新组件的模板:
|
||
|
||
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'template', 'app/hero-birthday2.component.ts (template)')(format=".")
|
||
|
||
:marked
|
||
We also added a button to the template and bound its click event to the component's `toggleFormat()` method.
|
||
That method toggles the component's `format` property between a short form
|
||
(`'shortDate'`) and a longer form (`'fullDate'`).
|
||
|
||
我们还能在模板中添加一个按钮,并把它的点击事件绑定到组件的`toggleFormat()`方法。
|
||
此方法会在短日期格式(`'shortDate'`)和长日期格式(`'fullDate'`)之间切换组件的`format`属性。
|
||
|
||
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'class', 'app/hero-birthday2.component.ts (class)')(format='.')
|
||
|
||
:marked
|
||
As we click the button, the displayed date alternates between
|
||
"**<samp>04/15/1988</samp>**" and
|
||
"**<samp>Friday, April 15, 1988</samp>**".
|
||
|
||
当我们点击按钮的时候,显示的日志会在"**<samp>04/15/1988</samp>**"和"**<samp>Friday, April 15, 1988</samp>**"之间切换。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/pipes/date-format-toggle-anim.gif' alt="Date Format Toggle")
|
||
:marked
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Learn more about the `DatePipes` format options in the [API Docs](../api/common/index/DatePipe-class.html).
|
||
|
||
要了解更多`DatePipes`的格式选项,请参阅[API文档](../api/common/index/DatePipe-class.html)。
|
||
|
||
:marked
|
||
## Chaining pipes
|
||
## 链式管道
|
||
|
||
We can chain pipes together in potentially useful combinations.
|
||
In the following example, we chain the birthday to the `DatePipe` and on to the `UpperCasePipe`
|
||
so we can display the birthday in uppercase. The following birthday displays as
|
||
**<samp>APR 15, 1988</samp>**.
|
||
|
||
我们可以把管道链在一起,以组合出一些潜在的有用功能。
|
||
下面这个例子中,我们把`birthday`链到`DatePipe`管道,然后又链到`UpperCasePipe`,这样我们就可以把生日显示成大写形式了。
|
||
比如下面的代码就会把生日显示成**<samp>APR 15, 1988</samp>**:
|
||
|
||
+makeExample('pipes/ts/app/app.component.html', 'chained-birthday')(format=".")
|
||
|
||
:marked
|
||
This example — which displays **<samp>FRIDAY, APRIL 15, 1988</samp>** —
|
||
chains the same pipes as above, but passes in a parameter to `date` as well.
|
||
|
||
下面这个显示**<samp>FRIDAY, APRIL 15, 1988</samp>**的例子用同样的方式链接了这两个管道,而且同时还给`date`管道传进去一个参数。
|
||
|
||
+makeExample('pipes/ts/app/app.component.html', 'chained-parameter-birthday')(format=".")
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Custom Pipes
|
||
## 自定义管道
|
||
|
||
We can write our own custom pipes.
|
||
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers:
|
||
|
||
我们还可以写自己的自定义管道。
|
||
下面就是一个名叫`ExponentialStrengthPipe`的管道,它可以放大英雄的能力:
|
||
|
||
+makeExample('pipes/ts/app/exponential-strength.pipe.ts', null, 'app/exponential-strength.pipe.ts')(format=".")
|
||
|
||
:marked
|
||
This pipe definition reveals several key points:
|
||
|
||
在这个管道的定义中体现了几个关键点:
|
||
|
||
* A pipe is a class decorated with pipe metadata.
|
||
|
||
* 管道是一个带有“管道元数据(pipe metadata)”装饰器的类。
|
||
|
||
* The pipe class implements the `PipeTransform` interface's `transform` method that
|
||
accepts an input value followed by optional parameters and returns the transformed value.
|
||
|
||
* 这个管道类实现了`PipeTransform`接口的`transform`方法,该方法接受一个输入值和一些可选参数,并返回转换后的值。
|
||
|
||
* There will be one additional argument to the `transform` method for each parameter passed to the pipe.
|
||
Our pipe has one such parameter: the `exponent`.
|
||
|
||
* 当每个输入值被传给`transform`方法时,还会带有一个额外的参数,比如我们这个管道中的`exponent`(放大指数)。
|
||
|
||
* We tell Angular that this is a pipe by applying the
|
||
`@Pipe` #{_decorator} which we import from the core Angular library.
|
||
|
||
* 我们通过`@Pipe`#{_decoratorCn}告诉Angular:这是一个管道。该#{_decoratorCn}是从Angular的`core`库中引入的。
|
||
|
||
* The `@Pipe` #{_decorator} allows us to define the
|
||
pipe name that we'll use within template expressions. It must be a valid JavaScript identifier.
|
||
Our pipe's name is `exponentialStrength`.
|
||
|
||
* 这个`@Pipe`#{_decoratorCn}允许我们定义管道的名字,这个名字会被用在模板表达式中。它必须是一个有效的JavaScript标识符。
|
||
比如,我们这个管道的名字是`exponentialStrength`。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
### The *PipeTransform* Interface
|
||
### *PipeTransform*接口
|
||
|
||
The `transform` method is essential to a pipe.
|
||
The `PipeTransform` *interface* defines that method and guides both tooling and the compiler.
|
||
It is technically optional; Angular looks for and executes the `transform` method regardless.
|
||
|
||
`transform`方法是管道的基本要素。
|
||
`PipeTransform`*接口*中定义了它,并用它指导各种工具和编译器。
|
||
从技术角度看,它是可选的。Angular不会管它,而是直接查找并执行`transform`方法。
|
||
|
||
:marked
|
||
Now we need a component to demonstrate our pipe.
|
||
|
||
现在,我们需要一个组件来演示这个管道。
|
||
+makeExample('pipes/ts/app/power-booster.component.ts',null,'app/power-booster.component.ts')(format='.')
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
||
|
||
:marked
|
||
Two things to note:
|
||
|
||
要注意的有两点:
|
||
1. We use our custom pipe the same way we use the built-in pipes.
|
||
|
||
1. 我们使用自定义管道的方式和内建管道完全相同。
|
||
|
||
1. We must include our pipe in the `declarations` #{_array} of the AppModule.
|
||
|
||
1. 我们必须在`AppModule`的`declarations`数组中包含这个管道。
|
||
|
||
.callout.is-helpful
|
||
header Remember the declarations #{_array}!
|
||
|
||
header 别忘了declarations数组!
|
||
|
||
:marked
|
||
Angular reports an error if we neglect to list our custom pipe.
|
||
We didn't list the `DatePipe` in our previous example because all
|
||
Angular built-in pipes are pre-registered.
|
||
Custom pipes must be registered manually.
|
||
|
||
如果我们忘了列出这个自定义管道,Angular就会报告一个错误。
|
||
在前一个例子中我们没有把`DatePipe`列进去,这是因为Angular所有的内建管道都已经预注册过了。
|
||
但自定义管道必须手工注册。
|
||
|
||
|
||
:marked
|
||
If we try the <live-example></live-example>,
|
||
we can probe its behavior by changing the value and the optional exponent in the template.
|
||
|
||
如果我们试一下这个<live-example></live-example>,就可以通过修改值和模板中的可选部分来体会其行为。
|
||
|
||
## Power Boost Calculator (extra-credit)
|
||
## 能力倍增计算器(加分项)
|
||
|
||
It's not much fun updating the template to test our custom pipe.
|
||
We could upgrade the example to a "Power Boost Calculator" that combines
|
||
our pipe and two-way data binding with `ngModel`.
|
||
|
||
仅仅升级模板来测试这个自定义管道其实没多大意思。
|
||
我们干脆把这个例子升级为“能力倍增计算器”,它可以把该管道和使用`ngModel`的双向数据绑定组合起来。
|
||
|
||
+makeExample('pipes/ts/app/power-boost-calculator.component.ts', null, '/app/power-boost-calculator.component.ts')(format='.')
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/pipes/power-boost-calculator-anim.gif' alt="Power Boost Calculator")
|
||
|
||
.l-main-section
|
||
a#change-detection
|
||
:marked
|
||
## Pipes and Change Detection
|
||
## 管道与变更检测
|
||
|
||
Angular looks for changes to data-bound values through a *change detection* process that runs after every JavaScript event:
|
||
every keystroke, mouse move, timer tick, and server response. This could be expensive.
|
||
Angular strives to lower the cost whenever possible and appropriate.
|
||
|
||
Angular通过*变更检测*过程来查找绑定值的更改,并在每一次JavaScript事件之后运行:每次按键、鼠标移动、定时器以及服务器的响应。
|
||
这可能会让变更检测显得很昂贵,但是Angular会尽可能降低变更检测的成本。
|
||
|
||
Angular picks a simpler, faster change detection algorithm when we use a pipe. Let's see how.
|
||
|
||
当我们使用管道时,Angular选用了一种简单、快速的变更检测算法。
|
||
|
||
### No pipe
|
||
### 无管道
|
||
|
||
The component in our next example uses the default, aggressive change detection strategy to monitor and update
|
||
its display of every hero in the `heroes` #{_array}. Here's the template:
|
||
|
||
我们下一个例子中的组件使用默认的、激进(昂贵)的变更检测策略来检测和更新`heroes`数组中的每个英雄。下面是它的模板:
|
||
|
||
+makeExample('pipes/ts/app/flying-heroes.component.html', 'template-1', 'app/flying-heroes.component.html (v1)')(format='.')
|
||
|
||
:marked
|
||
The companion component class provides heroes, adds new heroes into the #{_array}, and can reset the #{_array}.
|
||
|
||
和模板相伴的组件类可以提供英雄数组,能把新的英雄添加到数组中,还能重置英雄数组。
|
||
|
||
+makeExample('pipes/ts/app/flying-heroes.component.ts', 'v1', 'app/flying-heroes.component.ts (v1)')(format='.')
|
||
|
||
:marked
|
||
We can add a new hero and Angular updates the display when we do.
|
||
The `reset` button replaces `heroes` with a new #{_array} of the original heroes and Angular updates the display when we do.
|
||
If we added the ability to remove or change a hero, Angular would detect those changes too and update the display as well.
|
||
|
||
我们可以添加新的英雄,加完之后,Angular就会更新显示。
|
||
`reset`按钮会把`heroes`替换成一个由原来的英雄组成的新数组,重置完之后,Angular就会更新显示。
|
||
如果我们提供了删除或修改英雄的能力,Angular也会检测到那些更改,并更新显示。
|
||
|
||
### Flying Heroes pipe
|
||
### “会飞的英雄”管道
|
||
|
||
Let's add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroes to just those heroes who can fly.
|
||
|
||
我们来往`*ngFor`重复器中添加一个`FlyingHeroesPipe`管道,这个管道能过滤出所有会飞的英雄。
|
||
|
||
+makeExample('pipes/ts/app/flying-heroes.component.html', 'template-flying-heroes', 'app/flying-heroes.component.html (flyers)')(format='.')
|
||
:marked
|
||
Here's the `FlyingHeroesPipe` implementation which follows the pattern for custom pipes we saw earlier.
|
||
|
||
下面是`FlyingHeroesPipe`的实现,它遵循了我们以前见过的那些写自定义管道的模式。
|
||
|
||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pure', 'app/flying-heroes.pipe.ts')(format='.')
|
||
:marked
|
||
When we run the sample now we see odd behavior (try it in the <live-example></live-example>).
|
||
Every hero we add is a flying hero but none of them are displayed.
|
||
|
||
当运行例子时,我们看到一种奇怪的行为(试试<live-example></live-example>)。添加的每个英雄都是会飞行的英雄,但是没有一个被显示出来。
|
||
|
||
Although we're not getting the behavior we want, Angular isn't broken.
|
||
It's just using a different change detection algorithm — one that ignores changes to the list or any of its items.
|
||
|
||
虽然我们没有得到期望的行为,但Angular也没有出错。
|
||
这里只是用了另一种变更检测算法 —— 它会忽略对列表及其子项所做的任何更改。
|
||
|
||
Look at how we're adding a new hero:
|
||
|
||
来看看我们是如何添加新英雄的:
|
||
|
||
+makeExample('pipes/ts/app/flying-heroes.component.ts', 'push')(format='.')
|
||
:marked
|
||
We're adding the new hero into the `heroes` #{_array}. The reference to the #{_array} hasn't changed.
|
||
It's the same #{_array}. That's all Angular cares about. From its perspective, *same #{_array}, no change, no display update*.
|
||
|
||
当我们往`heroes`数组中添加一个新的英雄时,这个数组的引用并没有改变。它还是那个数组。而引用却是Angular所关心的一切。
|
||
从Angular的角度来看,*这是同一个数组,没有变化,也就不需要更新显示*。
|
||
|
||
We can fix that. Let's create a new #{_array} with the new hero appended and assign that to `heroes`.
|
||
This time Angular detects that the #{_array} reference has changed.
|
||
It executes the pipe and updates the display with the new #{_array} which includes the new flying hero.
|
||
|
||
我们可以修复它。让我们创建一个新数组,把这个英雄追加进去,并把它赋给`heroes`。
|
||
这次,Angular检测到数组的引用变化了。它执行了这个管道,并使用这个新数组更新显示,这次它就包括新的飞行英雄了。
|
||
|
||
*If we **mutate** the #{_array}, no pipe is invoked and no display updated;
|
||
if we **replace** the #{_array}, then the pipe executes and the display is updated*.
|
||
The *Flying Heroes* extends the
|
||
code with checkbox switches and additional displays to help us experience these effects.
|
||
|
||
如果我们**修改了**这个数组,没有管道被执行,也没有显示被更新。
|
||
如果我们**替换了**这个数组,管道就会被执行,显示也更新了。
|
||
这个*飞行英雄*的例子用检查框和额外的显示内容扩展了原有代码,来帮我们体验这些效果。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
|
||
|
||
:marked
|
||
Replacing the #{_array} is an efficient way to signal to Angular that it should update the display.
|
||
When do we replace the #{_array}? When the data change.
|
||
That's an easy rule to follow in *this toy* example
|
||
where the only way to change the data is by adding a new hero.
|
||
|
||
直接替换这个数组是通知Angular更新显示的一种高效方式。
|
||
我们该什么时候替换这个数组呢?当数据变化的时候。
|
||
在这个*玩具级*例子中,这是一个简单的规则,因为这里修改数据的唯一途径就是添加新英雄。
|
||
|
||
More often we don't know when the data have changed,
|
||
especially in applications that mutate data in many ways,
|
||
perhaps in application locations far away.
|
||
A component in such an application usually can't know about those changes.
|
||
Moreover, it's unwise to distort our component design to accommodate a pipe.
|
||
We strive as much as possible to keep the component class independent of the HTML.
|
||
The component should be unaware of pipes.
|
||
|
||
更多情况下,我们不知道什么时候数据变化了,尤其是在那些有很多种途径改动数据的程序中 —— 可能在程序中很远的地方。
|
||
组件就是一个通常无法知道那些改动的例子。此外,它会导致削足适履 —— 扭曲我们的组件设计来适应管道。
|
||
我们要尽可能保持组件类独立于HTML。组件不应该关心管道的存在。
|
||
|
||
Perhaps we should consider a different kind of pipe for filtering flying heroes, an *impure pipe*.
|
||
|
||
也行我们可以考虑用另一种管道来过滤会飞的英雄了,一个*非纯(impure)管道*。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Pure and Impure Pipes
|
||
## 纯(pure)管道与非纯(impure)管道
|
||
|
||
There are two categories of pipes: **pure** and **impure**.
|
||
Pipes are pure by default. Every pipe we've seen so far has been pure.
|
||
We make a pipe impure by setting its pure flag to false. We could make the `FlyingHeroesPipe`
|
||
impure like this:
|
||
|
||
有两类管道:**纯**的与**非纯**的。
|
||
默认情况下,管道都是纯的。我们以前见到的每个管道都是纯的。
|
||
通过把它的`pure`标志设置为`false`,我们可以制作一个非纯管道。我们可以像这样让`FlyingHeroesPipe`变成非纯的:
|
||
|
||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pipe-decorator')(format='.')
|
||
|
||
:marked
|
||
Before we do that, let's understand the difference between *pure* and *impure*, starting with a *pure* pipe.
|
||
|
||
在继续往下走之前,我们先理解一下*纯*和*非纯*之间的区别,从*纯*管道开始。
|
||
|
||
### Pure pipes
|
||
### 纯管道
|
||
|
||
block pure-change
|
||
:marked
|
||
Angular executes a *pure pipe* only when it detects a *pure change* to the input value.
|
||
A ***pure change*** is *either* a change to a primitive input value (`String`, `Number`, `Boolean`, `Symbol`)
|
||
*or* a changed object reference (`Date`, `Array`, `Function`, `Object`).
|
||
|
||
Angular只有在它检测到输入值发生了*纯变更*时才会执行*纯管道*。
|
||
***纯变更***是指对原始类型值(`String`、`Number`、`Boolean`、`Symbol`)的更改,
|
||
或者对对象引用(`Date`、`Array`、`Function`、`Object`)的更改。
|
||
|
||
:marked
|
||
Angular ignores changes *within* (composite) objects.
|
||
It won't call a pure pipe if we change an input month, add to an input #{_array}, or update an input object property.
|
||
|
||
Angular会忽略(复合)对象*内部*的更改。
|
||
如果我们更改了输入日期(`Date`)中的月份、往一个输入数组(`Array`)中添加新值或者更新了一个输入对象(`Object`)的属性,Angular都不会调用纯管道。
|
||
|
||
This may seem restrictive but is is also fast.
|
||
An object reference check is fast — much faster than a deep check for
|
||
differences — so Angular can quickly determine if it can skip both the
|
||
pipe execution and a view update.
|
||
|
||
这可能看起来是一种限制,但它保证了速度。
|
||
对象引用的检查是非常快的(比递归的深检查要快得多),所以Angular可以快速的决定是否应该跳过管道执行和视图更新。
|
||
|
||
For this reason, we prefer a pure pipe if we can live with the change detection strategy.
|
||
When we can't, we *may* turn to the impure pipe.
|
||
|
||
因此,如果我们要和变更检测策略打交道,就会更喜欢用纯管道。
|
||
如果不能,我们就*可以*转回到非纯管道。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Or we might not use a pipe at all.
|
||
It may be better to pursue the pipe's purpose with a property of the component,
|
||
a point we take up later.
|
||
|
||
或者我们也可以完全不用管道。
|
||
有时候,使用组件的属性能比用管道更好的达到目的,这一点我们等后面会再提起。
|
||
|
||
:marked
|
||
### Impure pipes
|
||
### 非纯管道
|
||
|
||
Angular executes an *impure pipe* during *every* component change detection cycle.
|
||
An impure pipe will be called a lot, as often as every keystroke or mouse-move.
|
||
|
||
Angular会在每个组件的变更检测周期中执行*非纯管道*。
|
||
非纯管道可能会被调用很多次,和每个按键或每次鼠标移动一样频繁。
|
||
|
||
With that concern in mind, we must implement an impure pipe with great care.
|
||
An expensive, long-running pipe could destroy the user experience.
|
||
|
||
要在脑子里绷着这根弦,我们必须小心翼翼的实现非纯管道。
|
||
一个昂贵、迟钝的管道将摧毁用户体验。
|
||
|
||
<a id="impure-flying-heroes"></a>
|
||
### An impure *FlyingHeroesPipe*
|
||
### 非纯版本的*FlyingHeroesPipe*
|
||
|
||
A flip of the switch turns our `FlyingHeroesPipe` into a `FlyingHeroesImpurePipe`.
|
||
Here's the complete implementation:
|
||
|
||
我们把`FlyingHeroesPipe`换成了`FlyingHeroesImpurePipe`。
|
||
下面是完整的实现:
|
||
+makeTabs(
|
||
'pipes/ts/app/flying-heroes.pipe.ts, pipes/ts/app/flying-heroes.pipe.ts',
|
||
'impure, pure',
|
||
'FlyingHeroesImpurePipe, FlyingHeroesPipe')(format='.')
|
||
|
||
:marked
|
||
We inherit from `FlyingHeroesPipe` to prove the point that nothing changed internally.
|
||
The only difference is the `pure` flag in the pipe metadata.
|
||
|
||
我们把它从`FlyingHeroesPipe`中继承下来,以证明无需改动内部代码。
|
||
唯一的区别是管道元数据中的`pure`标志。
|
||
|
||
This is a good candidate for an impure pipe because the `transform` function is trivial and fast.
|
||
|
||
这是一个很好地非纯管道候选者,因为它的`transform`函数又小又快。
|
||
|
||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts','filter')(format='.')
|
||
|
||
:marked
|
||
We can derive a `FlyingHeroesImpureComponent` that we derive from the `FlyingHeroesComponent`.
|
||
|
||
我们可以从`FlyingHeroesComponent`派生出一个`FlyingHeroesImpureComponent`。
|
||
|
||
+makeExample('pipes/ts/app/flying-heroes-impure.component.html','template-flying-heroes','app/flying-heroes-impure.component.html (FlyingHeroesImpureComponent)')(format='.')
|
||
|
||
:marked
|
||
The only substantive change is the pipe in the template.
|
||
We can confirm in the <live-example></live-example> that the _flying heroes_
|
||
display updates as we enter new heroes even when we mutate the `heroes` #{_array}.
|
||
|
||
p.
|
||
唯一的重大改动就是管道。
|
||
我们可以在#[+liveExampleLink('在线例子')]中确认,当我们输入新的英雄甚至修改#[code heroes]数组时,这个#[i 会飞的英雄]的显示也跟着更新了。
|
||
|
||
- var _dollar = _docsFor === 'ts' ? '$' : '';
|
||
h3#async-pipe The impure #[i AsyncPipe]
|
||
h3#async-pipe 非纯管道#[i AsyncPipe]
|
||
:marked
|
||
The Angular `AsyncPipe` is an interesting example of an impure pipe.
|
||
The `AsyncPipe` accepts a `#{_Promise}` or `#{_Observable}` as input
|
||
and subscribes to the input automatically, eventually returning the emitted value(s).
|
||
|
||
Angular的`AsyncPipe`是一个有趣的非纯管道的例子。
|
||
`AsyncPipe`接受一个`Promise`或`Observable`作为输入,并且自动订阅这个输入,最终返回它们给出的值。
|
||
|
||
It is also stateful.
|
||
The pipe maintains a subscription to the input `#{_Observable}` and
|
||
keeps delivering values from that `#{_Observable}` as they arrive.
|
||
|
||
而且它是有状态的。
|
||
该管道维护着一个所输入的`Observable`的订阅,并且持续从那个`Observable`中发出新到的值。
|
||
|
||
In this next example, we bind an `#{_Observable}` of message strings
|
||
(`message#{_dollar}`) to a view with the `async` pipe.
|
||
|
||
在下面例子中,我们使用该`async`管道把一个消息字符串(`message#{_dollar}`)的`#{_Observable}`绑定到视图中。
|
||
|
||
+makeExample('pipes/ts/app/hero-async-message.component.ts', null, 'app/hero-async-message.component.ts')
|
||
|
||
:marked
|
||
The Async pipe saves boilerplate in the component code.
|
||
The component doesn't have to subscribe to the async data source,
|
||
it doesn't extract the resolved values and expose them for binding,
|
||
and the component doesn't have to unsubscribe when it is destroyed
|
||
(a potent source of memory leaks).
|
||
|
||
这个Async管道节省了组件的样板代码。
|
||
组件不用订阅这个异步数据源,而且不用在被销毁时取消订阅(如果订阅了而忘了反订阅容易导致隐晦的内存泄露)。
|
||
|
||
### An impure caching pipe
|
||
### 一个非纯而且带缓存的管道
|
||
|
||
Let's write one more impure pipe, a pipe that makes an HTTP request to the server.
|
||
Normally, that's a horrible idea.
|
||
It's probably a horrible idea no matter what we do.
|
||
We're forging ahead anyway to make a point.
|
||
Remember that impure pipes are called every few microseconds.
|
||
If we're not careful, this pipe will punish the server with requests.
|
||
|
||
我们来写更多的非纯管道:一个向服务器发起HTTP请求的管道。
|
||
这通常是一个可怕的主意。
|
||
不管我们怎么做,估计它都会是一个可怕的主意。
|
||
但即便如此,为了讲解这个技术点,我们还是写一个吧。
|
||
时刻记住,非纯管道可能每隔几微秒就会被调用一次。
|
||
如果我们不小心点儿,这个管道就会发起一大堆请求“攻击”服务器。
|
||
|
||
We are careful. Our pipe only makes a server call if the request URL has changed.
|
||
It caches the request URL and waits for a result which it also caches when it arrives.
|
||
The pipe returns the cached result (which is null while a request is in flight)
|
||
after every Angular call and only contacts the server as necessary.
|
||
|
||
我们确实得小心点儿。所以这个管道只有当所请求的URL发生变化时才会向服务器发起请求。
|
||
它会缓存这个请求的URL,并等待一个结果,当结果发回来时,就缓存它。
|
||
以后每当Angular调用此管道时,它都会返回这个缓存的结果(当请求尚未返回时,此结果是空),只在必要时才会去联系服务器。
|
||
|
||
Here's the code, which uses the [Angular http](server-communication.html) facility
|
||
to retrieve a `heroes.json` file:
|
||
|
||
下面就是使用[Angular http](server-communication.html)获取`heroes.json`文件的代码:
|
||
|
||
+makeExample('pipes/ts/app/fetch-json.pipe.ts', null, 'app/fetch-json.pipe.ts')
|
||
:marked
|
||
Then we demonstrate it in a harness component whose template defines two bindings to this pipe.
|
||
|
||
接下来我们用一个测试台组件演示一下它,该组件的模板中定义了两个使用到此管道的绑定。
|
||
+makeExample('pipes/ts/app/hero-list.component.ts', 'template', 'app/hero-list.component.ts (template)')
|
||
:marked
|
||
Despite the two bindings and what we know to be frequent pipe calls,
|
||
the nework tab in the browser developer tools confirms that there is only one request for the file.
|
||
|
||
尽管这里有两个绑定,并且我们知道它们频繁的调用了管道,但在浏览器的开发者工具中的“Network”页,我们可以确认只发起了一次到该文件的请求。
|
||
|
||
The component renders like this:
|
||
|
||
组件渲染起来是这样的:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
|
||
|
||
:marked
|
||
### *JsonPipe*
|
||
|
||
The second binding involving the `FetchPipe` uses more pipe chaining.
|
||
We take the same fetched results displayed in the first binding
|
||
and display them again, this time in JSON format by chaining through to the built-in `JsonPipe`.
|
||
|
||
第二个绑定除了用到`FetchPipe`之外还链接了更多管道。
|
||
我们把获取数据的结果同时显示在第一个绑定和第二个绑定中。第二个绑定中,我们通过链接到一个内建管道`JsonPipe`把它转成了JSON格式。
|
||
|
||
.callout.is-helpful
|
||
header Debugging with the json pipe
|
||
header 借助json管道进行调试
|
||
:marked
|
||
The [JsonPipe](../api/common/index/JsonPipe-class.html)
|
||
provides an easy way to diagnosis a mysteriously failing data binding or
|
||
inspect an object for future binding.
|
||
|
||
[JsonPipe](../api/common/index/JsonPipe-class.html)为你诊断数据绑定的某些神秘错误或为做进一步绑定而探查数据时,提供了一个简单途径。
|
||
|
||
:marked
|
||
Here's the complete component implementation:
|
||
|
||
下面是组件完整的实现代码:
|
||
|
||
+makeExample('pipes/ts/app/hero-list.component.ts', null, 'app/hero-list.component.ts')
|
||
|
||
a(id="pure-pipe-pure-fn")
|
||
:marked
|
||
### Pure pipes and pure functions
|
||
### 纯管道与纯函数
|
||
|
||
A pure pipe uses pure functions.
|
||
Pure functions process inputs and return values without detectable side-effects.
|
||
Given the same input they should always return the same output.
|
||
|
||
纯管道使用纯函数。
|
||
纯函数是指在处理输入并返回结果时,不会产生任何副作用的函数。
|
||
给定相同的输入,它们总是返回相同的输出。
|
||
|
||
The pipes we saw earlier in this chapter were implemented with pure functions.
|
||
The built-in `DatePipe` is a pure pipe with a pure function implementation.
|
||
So is our `ExponentialStrengthPipe`.
|
||
So is our `FlyingHeroesPipe`.
|
||
A few steps back we reviewed the `FlyingHeroesImpurePipe` — *an impure pipe with a pure function*.
|
||
|
||
我们在本章前面见过的管道都是用纯函数实现的。
|
||
内建的`DatePipe`就是一个用纯函数实现的纯管道。
|
||
`ExponentialStrengthPipe`是如此,
|
||
`FlyingHeroesComponent`也是如此。
|
||
不久前我们刚看过的`FlyingHeroesImpurePipe`,是一个*用纯函数实现的非纯管道*。
|
||
|
||
But a *pure pipe* must always be implemented with a *pure function*. Failure to heed this warning will bring about many a console errors regarding expressions that have changed after they were checked.
|
||
|
||
但是一个*纯管道*必须总是用*纯函数*实现。忽略这个警告将导致失败并带来一大堆这样的控制台错误:表达式在被检查后被变更。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Next Steps
|
||
## 下一步
|
||
|
||
Pipes are a great way to encapsulate and share common display-value
|
||
transformations. We use them like styles, dropping them
|
||
into our templates expressions to enrich the appeal and usability
|
||
of our views.
|
||
|
||
管道能很好的封装和共享的通用“值-显示”转换逻辑。我们可以像样式一样使用它们,把它们扔到模板表达式中,以提升视图的表现力和可用性。
|
||
|
||
Explore Angular's inventory of built-in pipes in the [API Reference](../api/#!?apiFilter=pipe).
|
||
Try writing a custom pipe and perhaps contributing it to the community.
|
||
|
||
要浏览Angular的所有内建管道,请到[API参考手册](../api/#!?apiFilter=pipe)。
|
||
学着写写自定义管道,并贡献给开发社区。
|
||
|
||
a(id="no-filter-pipe")
|
||
.l-main-section
|
||
:marked
|
||
## No *FilterPipe* or *OrderByPipe*
|
||
## 没有*FilterPipe*或者*OrderByPipe*
|
||
|
||
Angular does not ship with pipes for filtering or sorting lists.
|
||
Developers familiar with Angular 1 know these as `filter` and `orderBy`.
|
||
There are no equivalents in Angular 2.
|
||
|
||
Angular没有随身发布过滤或列表排序的管道。
|
||
熟悉Angular 1的开发人员应该知道`filter`和`orderBy`过滤器,但在Angular 2中它们没有等价物。
|
||
|
||
This is not an oversight. Angular 2 is unlikely to offer such pipes because
|
||
(a) they perform poorly and (b) they prevent aggressive minification.
|
||
Both `filter` and `orderBy` require parameters that reference object properties.
|
||
We learned earlier that such pipes must be [*impure*](#pure-and-impure-pipes) and that
|
||
Angular calls impure pipes in almost every change detection cycle.
|
||
|
||
这并不是疏忽。Angular 2不想提供这些管道,因为 (a) 它们性能堪忧,以及 (b) 它们会阻止比较激进的代码最小化(minification)。
|
||
无论是`filter`还是`orderBy`都需要它的参数引用对象型属性。
|
||
我们前面学过,这样的管道必然是[*非纯管道*](#pure-and-impure-pipes),并且Angular会在几乎每一次变更检测周期中调用非纯管道。
|
||
|
||
Filtering and especially sorting are expensive operations.
|
||
The user experience can degrade severely for even moderate sized lists when Angular calls these pipe methods many times per second.
|
||
The `filter` and `orderBy` have often been abused in Angular 1 apps, leading to complaints that Angular itself is slow.
|
||
That charge is fair in the indirect sense that Angular 1 prepared this performance trap
|
||
by offering `filter` and `orderBy` in the first place.
|
||
|
||
过滤、 特别是排序是昂贵的操作。
|
||
当Angular每秒调用很多次这类管道函数时,即使是中等规模的列表都可能严重降低用户体验。
|
||
在Angular 1程序中,`filter`和`orderBy`经常被误用,结果连累到Angular自身,人们抱怨说它太慢。
|
||
从某种意义上,这也不冤:谁叫Angular 1把`filter`和`orderBy`作为首发队员呢?是它自己准备了这个性能陷阱。
|
||
|
||
The minification hazard is also compelling if less obvious. Imagine a sorting pipe applied to a list of heroes.
|
||
We might sort the list by hero `name` and `planet` of origin properties something like this:
|
||
|
||
虽然不是很明显,但代码最小化方面也存在风险。想象一个用于英雄列表的排序管道。我们可能根据英雄原始属性中的`name`和`planet`进行排序,就像这样:
|
||
code-example(language="html")
|
||
<!-- NOT REAL CODE! -->
|
||
<div *ngFor="let hero of heroes | orderBy:'name,planet'"></div>
|
||
:marked
|
||
We identify the sort fields by text strings, expecting the pipe to reference a property value by indexing
|
||
(e.g., `hero['name']`).
|
||
Unfortunately, aggressive minification *munges* the `Hero` property names so that `Hero.name` and `Hero.planet`
|
||
becomes something like `Hero.a` and `Hero.b`. Clearly `hero['name']` is not going to work.
|
||
|
||
我们使用文本字符串来标记出排序字段,期望管道通过索引形式(如`hero['name']`)引用属性的值。
|
||
不幸的是,激进的代码最小化策略会*改变*`Hero`类的属性名,所以`Hero.name`和`Hero.planet`可能会被变成`Hero.a`和`Hero.b`。
|
||
显然,`hero['name']`是无法正常工作的。
|
||
|
||
Some of us may not care to minify this aggressively. That's *our* choice.
|
||
But the Angular product should not prevent someone else from minifying aggressively.
|
||
Therefore, the Angular team decided that everything shipped in Angular will minify safely.
|
||
|
||
我们中的一些人可能不想做那么激进的最小化。但那不过是*我们的*选择而已。
|
||
Angular作为一个产品不应该拒绝那些想做激进的最小化的人。
|
||
所以,Angular开发组决定随Angular一起发布的每样东西,都应该能被安全的最小化。
|
||
|
||
The Angular team and many experienced Angular developers strongly recommend that you move
|
||
filtering and sorting logic into the component itself.
|
||
The component can expose a `filteredHeroes` or `sortedHeroes` property and take control
|
||
over when and how often to execute the supporting logic.
|
||
Any capabilities that you would have put in a pipe and shared across the app can be
|
||
written in a filtering/sorting service and injected into the component.
|
||
|
||
Angular开发组和一些有经验的Angular开发者强烈建议你:把你的过滤和排序逻辑挪进组件本身。
|
||
组件可以对外暴露一个`filteredHeroes`或`sortedHeroes`属性,这样它就获得控制权,以决定要用什么频度去执行其它辅助逻辑。
|
||
你原本准备实现为管道,并在整个应用中共享的那些功能,都能被改写为一个过滤/排序的服务,并注入到组件中。
|
||
|
||
If these performance and minification considerations do not apply to you, you can always create your own such pipes
|
||
(along the lines of the [FlyingHeroesPipe](#impure-flying-heroes)) or find them in the community.
|
||
|
||
如果你不需要顾虑这些性能和最小化问题,也可以创建自己的管道来实现这些功能(参考[FlyingHeroesPipe](#impure-flying-heroes)中的写法)或到社区中去找找。
|