docs(aio): merge latest chinese translations

This commit is contained in:
Zhicheng Wang 2017-07-23 11:51:25 +08:00
parent 061475402c
commit 20a55c54ed
57 changed files with 23990 additions and 1331 deletions

View File

@ -0,0 +1,54 @@
@description
## Transpile
## 转译transpile)
<div class="l-sub-section">
The process of transforming code written in one form of JavaScript
(such as TypeScript) into another form of JavaScript (such as [ES5](_fragments/glossary-t2#es5)).
把一种形式的 JavaScript例如 TypeScript转换成另一种形式的 JavaScript例如 [ES5](_fragments/glossary-t2#es5))的过程。
</div>
## TypeScript
<div class="l-sub-section">
A version of JavaScript that supports most [ECMAScript 2015](_fragments/glossary-t2#es2015)
language features such as [decorators](_fragments/glossary-t2#decorator).
JavaScript 的一个版本,支持了几乎所有 [ECMAScript 2015](_fragments/glossary-t2#ecmascript=2015) 语言特性,例如[装饰器 (decorator)](_fragments/glossary-t2#decorator))。
TypeScript is also notable for its optional typing system, which provides
compile-time type checking and strong tooling support (such as "intellisense,"
code completion, refactoring, and intelligent search). Many code editors
and IDEs support TypeScript either natively or with plugins.
TypeScript 还以它的可选类型系统而著称。
该类型系统提供了编译时类型检查和强大的工具支持(例如 “Intellisense”代码补齐重构和智能搜索等
许多代码编辑器和 IDE 都原生支持 TypeScript 或通过插件提供支持。
TypeScript is the preferred language for Angular development, although
you can use other JavaScript dialects such as [ES5](_fragments/glossary-t2#es5).
TypeScript 是 Angular 的首选语言,当然,你可以使用其它 JavaScript 方言,例如[ES5](_fragments/glossary-t2#es5)。
Read more about TypeScript at [typescriptlang.org](http://www.typescriptlang.org/).
更多信息,见[typescript.org](http://www.typescriptlang.org/)。
</div>

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
@title
Animations
动画
@intro
A guide to Angular's animation system.
Angular 动画系统指南。
@description
@ -12,10 +12,16 @@ user interfaces transition smoothly between states with engaging animations
that call attention where it's needed. Well-designed animations can make a UI not only
more fun but also easier to use.
动画是现代Web应用设计中一个很重要的方面。我们希望用户界面能在不同的状态之间更平滑的转场。如果需要还可以用适当的动画来吸引注意力。
设计良好的动画不但会让UI更有趣还会让它更容易使用。
Angular's animation system lets you build animations that run with the same kind of native
performance found in pure CSS animations. You can also tightly integrate your
animation logic with the rest of your application code, for ease of control.
Angular的动画系统赋予了制作各种动画效果的能力以构建出与原生CSS动画性能相同的动画。
我们也获得了额外的让动画逻辑与其它应用代码紧紧集成在一起的能力,这让动画可以被更容易的触发与控制。
<div class="alert is-helpful">
@ -24,10 +30,13 @@ animation logic with the rest of your application code, for ease of control.
Angular animations are built on top of the standard [Web Animations API](https://w3c.github.io/web-animations/)
and run natively on [browsers that support it](http://caniuse.com/#feat=web-animation).
Angular动画是基于标准的[Web动画API(Web Animations API)](https://w3c.github.io/web-animations/)构建的,它们在[支持此API的浏览器中](http://caniuse.com/#feat=web-animation)会用原生方式工作。
For other browsers, a polyfill is required. Grab
[`web-animations.min.js` from GitHub](https://github.com/web-animations/web-animations-js) and
add it to your page.
至于其它浏览器,就需要一个填充库(polyfill)了。你可以[从这里获取`web-animations.min.js`](https://github.com/web-animations/web-animations-js),并把它加入你的页面中。
</div>
@ -36,16 +45,47 @@ add it to your page.
# Contents
* [Example: Transitioning between two states](guide/animations#example-transitioning-between-states).
* [States and transitions](guide/animations#states-and-transitions).
# 目录
* [Example: Transitioning between two states](guide/animations#example-transitioning-between-states)
[范例:在两个状态之间进行转场(Transition)](guide/animations#example-transitioning-between-states)
* [States and transitions](guide/animations#states-and-transitions)
[状态与转场](guide/animations#states-and-transitions)
* [Example: Entering and leaving](guide/animations#example-entering-and-leaving).
[范例:进场与离场](guide/animations#example-entering-and-leaving)
* [Example: Entering and leaving from different states](guide/animations#example-entering-and-leaving-from-different-states).
* [Animatable properties and units](guide/animations#animatable-properties-and-units).
* [Automatic property calculation](guide/animations#automatic-property-calculation).
* [Animation timing](guide/animations#animation-timing).
* [Multi-step animations with keyframes](guide/animations#multi-step-animations-with-keyframes).
* [Parallel animation groups](guide/animations#parallel-animation-groups).
* [Animation callbacks](guide/animations#animation-callbacks).
[范例:从其它状态进场与离场](guide/animations#example-entering-and-leaving-from-different-states)
* [Animatable properties and units](guide/animations#animatable-properties-and-units).
[可动的(Animatable)属性与单位](guide/animations#animatable-properties-and-units)
* [Automatic property calculation](guide/animations#automatic-property-calculation).
[自动属性值计算](guide/animations#automatic-property-calculation)
* [Animation timing](guide/animations#animation-timing).
[动画时间线(Timing)](guide/animations#animation-timing)
* [Multi-step animations with keyframes](guide/animations#multi-step-animations-with-keyframes).
[基于关键帧(Keyframes)的多阶段动画](guide/animations#multi-step-animations-with-keyframes)
* [Parallel animation groups](guide/animations#parallel-animation-groups).
[并行动画组(Group)](guide/animations#parallel-animation-groups)
* [Animation callbacks](guide/animations#animation-callbacks).
[动画回调](guide/animations#animation-callbacks)
<div class="l-sub-section">
@ -54,6 +94,8 @@ add it to your page.
The examples in this page are available as a <live-example></live-example>.
本章中引用的这个例子可以到<live-example></live-example>去体验。
</div>
@ -65,6 +107,9 @@ The examples in this page are available as a <live-example></live-example>.
## Quickstart example: Transitioning between two states
## 快速起步范例:在两个状态间转场
<figure>
<img src="assets/images/devguide/animations/animation_basic_click.gif" alt="A simple transition animation" align="right" style="width:220px;margin-left:20px"></img>
</figure>
@ -74,9 +119,13 @@ The examples in this page are available as a <live-example></live-example>.
You can build a simple animation that transitions an element between two states
driven by a model attribute.
我们来构建一个简单的动画,它会让一个元素用模型驱动的方式在两个状态之间转场。
Animations are defined inside `@Component` metadata. Before you can add animations, you need
to import a few animation-specific imports and functions:
动画会被定义在`@Component`元数据中。在添加动画之前,先引入一些与动画有关的函数:
<code-example path="animations/src/app/app.module.ts" region="animations-module" title="app.module.ts (@NgModule imports excerpt)" linenums="false">
@ -94,6 +143,9 @@ With these, you can define an *animation trigger* called `heroState` in the comp
metadata. It uses animations to transition between two states: `active` and `inactive`. When a
hero is active, the element appears in a slightly larger size and lighter color.
通过这些,可以在组件元数据中定义一个名叫`heroState`的*动画触发器*。它在两个状态`active`和`inactive`之间进行转场。
当英雄处于激活状态时,它会把该元素显示得稍微大一点、亮一点。
<code-example path="animations/src/app/hero-list-basic.component.ts" region="animationdef" title="hero-list-basic.component.ts (@Component excerpt)" linenums="false">
@ -108,6 +160,8 @@ hero is active, the element appears in a slightly larger size and lighter color.
In this example, you are defining animation styles (color and transform) inline in the
animation metadata.
在这个例子中,我们在元数据中用内联的方式定义了动画样式(`color`和`transform`)。在即将到来的一个Angular版本中还将支持从组件的CSS样式表中提取样式。
</div>
@ -116,6 +170,8 @@ animation metadata.
Now, using the `[@triggerName]` syntax, attach the animation that you just defined to
one or more elements in the component's template.
我们刚刚定义了一个动画,但它还没有被用到任何地方。要想使用它,可以在模板中用`[@triggerName]`语法来把它附加到一个或多个元素上。
<code-example path="animations/src/app/hero-list-basic.component.ts" region="template" title="hero-list-basic.component.ts (excerpt)" linenums="false">
@ -127,9 +183,14 @@ Here, the animation trigger applies to every element repeated by an `ngFor`. Eac
the repeated elements animates independently. The value of the
attribute is bound to the expression `hero.state` and is always either `active` or `inactive`.
这里,我们把该动画触发器添加到了由`ngFor`重复出来的每一个元素上。每个重复出来的元素都有独立的动画效果。
然后把`@triggerName`属性(Attribute)的值设置成表达式`hero.state`。这个值应该或者是`inactive`或者是`active`,因为我们刚刚为它们俩定义过动画状态。
With this setup, an animated transition appears whenever a hero object changes state.
Here's the full component implementation:
通过这些设置,一旦英雄对象的状态发生了变化,就会触发一个转场动画。下面是完整的组件实现:
<code-example path="animations/src/app/hero-list-basic.component.ts" title="hero-list-basic.component.ts">
@ -139,16 +200,25 @@ Here's the full component implementation:
## States and transitions
## 状态与转场
Angular animations are defined as logical **states** and **transitions**
between states.
Angular动画是由**状态**和**状态之间的转场效果**所定义的。
An animation state is a string value that you define in your application code. In the example
above, the states `'active'` and `'inactive'` are based on the logical state of
hero objects. The source of the state can be a simple object attribute, as it was in this case,
or it can be a value computed in a method. The important thing is that you can read it into the
component's template.
You can define *styles* for each animation state:
动画状态是一个由程序代码中定义的字符串值。在上面的例子中,基于英雄对象的逻辑状态,我们使用了`'active'`和`'inactive'`这两种状态。
状态的来源可以是像本例中这样简单的对象属性,也可以是由方法计算出来的值。重点是,我们得能从组件模板中读取它。
We can define *styles* for each animation state:
我们可以为每个动画状态定义了*一组样式*
<code-example path="animations/src/app/hero-list-basic.component.ts" region="states" title="src/app/hero-list-basic.component.ts" linenums="false">
@ -161,9 +231,14 @@ These `state` definitions specify the *end styles* of each state.
They are applied to the element once it has transitioned to that state, and stay
*as long as it remains in that state*. In effect, you're defining what styles the element has in different states.
这些`state`具体定义了每个状态的*最终样式*。一旦元素转场到那个状态,该样式就会被应用到此元素上,*当它留在此状态时*,这些样式也会一直保持着。
从这个意义上讲,这里其实并不只是在定义动画,而是在定义该元素在不同状态时应该具有的样式。
After you define states, you can define *transitions* between the states. Each transition
controls the timing of switching between one set of styles and the next:
定义完状态,就能定义在状态之间的各种*转场*了。每个转场都会控制一条在一组样式和下一组样式之间切换的时间线:
<code-example path="animations/src/app/hero-list-basic.component.ts" region="transitions" title="src/app/hero-list-basic.component.ts" linenums="false">
@ -180,6 +255,8 @@ controls the timing of switching between one set of styles and the next:
If several transitions have the same timing configuration, you can combine
them into the same `transition` definition:
如果多个转场都有同样的时间线配置,就可以把它们合并进同一个`transition`定义中:
<code-example path="animations/src/app/hero-list-combined-transitions.component.ts" region="transitions" title="src/app/hero-list-combined-transitions.component.ts" linenums="false">
@ -190,6 +267,8 @@ them into the same `transition` definition:
When both directions of a transition have the same timing, as in the previous
example, you can use the shorthand syntax `<=>`:
如果要对同一个转场的两个方向都使用相同的时间线(就像前面的例子中那样),就可以使用`<=>`这种简写语法:
<code-example path="animations/src/app/hero-list-twoway.component.ts" region="transitions" title="src/app/hero-list-twoway.component.ts" linenums="false">
@ -203,6 +282,9 @@ the element receives one set of styles immediately and is then animated to the n
When the transition finishes, none of these styles are kept because they're not
defined in a `state`.
有时希望一些样式只在动画期间生效,但在结束后并不保留它们。这时可以把这些样式内联在`transition`中进行定义。
在这个例子中,该元素会立刻获得一组样式,然后动态转场到下一个状态。当转场结束时,这些样式并不会被保留,因为它们并没有被定义在`state`中。
<code-example path="animations/src/app/hero-list-inline-styles.component.ts" region="transitions" title="src/app/hero-list-inline-styles.component.ts" linenums="false">
@ -212,12 +294,21 @@ defined in a `state`.
### The wildcard state `*`
### `*`(通配符)状态
The `*` ("wildcard") state matches *any* animation state. This is useful for defining styles and
transitions that apply regardless of which state the animation is in. For example:
`*`(通配符)状态匹配*任何*动画状态。当定义那些不需要管当前处于什么状态的样式及转场时,这很有用。比如:
* The `active => *` transition applies when the element's state changes from `active` to anything else.
当该元素的状态从`active`变成任何其它状态时,`active => *`转场都会生效。
* The `* => *` transition applies when *any* change between two states takes place.
当在*任意*两个状态之间切换时,`* => *`转场都会生效。
<figure class='image-display'>
<img src="assets/images/devguide/animations/ng_animate_transitions_inactive_active_wildcards.png" alt="The wildcard state can be used to match many different transitions at once" width="400"></img>
@ -227,14 +318,21 @@ transitions that apply regardless of which state the animation is in. For exampl
### The `void` state
### `void`状态
The special state called `void` can apply to any animation. It applies
when the element is *not* attached to a view, perhaps because it has not yet been
added or because it has been removed. The `void` state is useful for defining enter and
leave animations.
有一种叫做`void`的特殊状态,它可以应用在任何动画中。它表示元素*没有*被附加到视图。这种情况可能是由于它尚未被添加进来或者已经被移除了。
`void`状态在定义“进场”和“离场”的动画时会非常有用。
For example the `* => void` transition applies when the element leaves the view,
regardless of what state it was in before it left.
比如当一个元素离开视图时,`* => void`转场就会生效,而不管它在离场以前是什么状态。
<figure class='image-display'>
<img src="assets/images/devguide/animations/ng_animate_transitions_void_in.png" alt="The void state can be used for enter and leave transitions" width="400"></img>
@ -244,8 +342,13 @@ regardless of what state it was in before it left.
The wildcard state `*` also matches `void`.
`*`通配符状态也能匹配`void`。
## Example: Entering and leaving
## 例子:进场与离场
<figure>
<img src="assets/images/devguide/animations/animation_enter_leave.gif" alt="Enter and leave animations" align="right" style="width:250px;"></img>
</figure>
@ -255,12 +358,22 @@ The wildcard state `*` also matches `void`.
Using the `void` and `*` states you can define transitions that animate the
entering and leaving of elements:
使用`void`和`*`状态,可以定义元素进场与离场时的转场动画:
* Enter: `void => *`
进场:`void => *`
* Leave: `* => void`
离场:`* => void`
For example, in the `animations` array below there are two transitions that use
the `void => *` and `* => void` syntax to animate the element in and out of the view.
例如,在下面的`animations`数组中,这两个转场语句使用`void => *`和`* => void`语法来让该元素以动画形式进入和离开当前视图。
<code-example path="animations/src/app/hero-list-enter-leave.component.ts" region="animationdef" title="hero-list-enter-leave.component.ts (excerpt)" linenums="false">
</code-example>
@ -272,6 +385,9 @@ transition definitions, and not in a separate `state(void)` definition. Thus, th
are different on enter and leave: the element enters from the left
and leaves to the right.
注意,在这个例子中,这些样式在转场定义中被直接应用到了`void`状态,但并没有一个单独的`state(void)`定义。
这么做是因为希望在进场与离场时使用不一样的转换效果:元素从左侧进场,从右侧离开。
<div class="l-sub-section">
@ -279,6 +395,8 @@ and leaves to the right.
These two common animations have their own aliases:
这两个常见的动画有自己的别名:
<code-example language="typescript">
transition(':enter', [ ... ]); // void => *
transition(':leave', [ ... ]); // * => void
@ -293,6 +411,9 @@ These two common animations have their own aliases:
## Example: Entering and leaving from different states
## 范例:从不同的状态下进场和离场
<figure>
<img src="assets/images/devguide/animations/animation_enter_leave_states.gif" alt="Enter and leave animations combined with state animations" align="right" style="width:200px"></img>
</figure>
@ -304,13 +425,28 @@ using the hero state as the animation state. This lets you configure
different transitions for entering and leaving based on what the state of the hero
is:
通过把英雄的状态用作动画的状态,还能把该动画跟以前的转场动画组合成一个复合动画。这让我们能根据该英雄的当前状态为其配置不同的进场与离场动画:
* Inactive hero enter: `void => inactive`
非激活英雄进场:`void => inactive`
* Active hero enter: `void => active`
激活英雄进场:`void => active`
* Inactive hero leave: `inactive => void`
非激活英雄离场:`inactive => void`
* Active hero leave: `active => void`
激活英雄离场:`active => void`
This gives you fine-grained control over each transition:
现在就对每一种转场都有了细粒度的控制:
<figure class='image-display'>
<img src="assets/images/devguide/animations/ng_animate_transitions_inactive_active_void.png" alt="This example transitions between active, inactive, and void states" width="400"></img>
@ -326,25 +462,38 @@ This gives you fine-grained control over each transition:
## Animatable properties and units
## 可动的(Animatable)属性与单位
Since Angular's animation support builds on top of Web Animations, you can animate any property
that the browser considers *animatable*. This includes positions, sizes, transforms, colors,
borders, and many others. The W3C maintains
[a list of animatable properties](https://www.w3.org/TR/css3-transitions/#animatable-properties)
on its [CSS Transitions page](https://www.w3.org/TR/css3-transitions).
由于Angular的动画支持是基于Web Animations标准的所以也能支持浏览器认为可以*参与动画*的任何属性。这些属性包括位置(position)、大小(size)、变换(transform)、颜色(color)、边框(border)等很多属性。W3C维护着
[一个“可动”属性列表](https://www.w3.org/TR/css3-transitions/#animatable-properties)。
For positional properties that have a numeric value, you can define a unit by providing
the value as a string with the appropriate suffix:
尺寸类属性(如位置、大小、边框等)包括一个数字值和一个用来定义长度单位的后缀:
* `'50px'`
* `'3em'`
* `'100%'`
If you don't provide a unit when specifying dimension, Angular assumes the default of `px`:
对大多数尺寸类属性而言,还能只定义一个数字,那就表示它使用的是像素(px)数:
* `50` is the same as saying `'50px'`
`50`相当于`'50px'`
## Automatic property calculation
## 自动属性值计算
<figure>
<img src="assets/images/devguide/animations/animation_auto.gif" alt="Animation with automated height calculation" align="right" style="width:220px;margin-left:20px"></img>
</figure>
@ -356,12 +505,18 @@ For example, elements often have widths and heights that
depend on their content and the screen size. These properties are often tricky
to animate with CSS.
有时候我们想在动画中使用的尺寸类样式它的值在开始运行之前都是不可知的。比如元素的宽度和高度往往依赖于它们的内容和屏幕的尺寸。处理这些属性对CSS动画而言通常是相当棘手的。
In these cases, you can use a special `*` property value so that the value of the
property is computed at runtime and then plugged into the animation.
如果用Angular动画就可以用一个特殊的`*`属性值来处理这种情况。该属性的值将会在运行期被计算出来,然后插入到这个动画中。
In this example, the leave animation takes whatever height the element has before it
leaves and animates from that height to zero:
这个例子中的“离场”动画会取得该元素在离场前的高度并且把它从这个高度用动画转场到0高度
<code-example path="animations/src/app/hero-list-auto.component.ts" region="animationdef" title="src/app/hero-list-auto.component.ts" linenums="false">
@ -371,38 +526,69 @@ leaves and animates from that height to zero:
## Animation timing
## 动画时间线
There are three timing properties you can tune for every animated transition:
the duration, the delay, and the easing function. They are all combined into
a single transition *timing string*.
对每一个动画转场效果,有三种时间线属性可以调整:持续时间(duration)、延迟(delay)和缓动(easing)函数。它们被合并到了一个单独的*转场时间线字符串*。
### Duration
### 持续时间
The duration controls how long the animation takes to run from start to finish.
You can define a duration in three ways:
持续时间控制动画从开始到结束要花多长时间。可以用三种方式定义持续时间:
* As a plain number, in milliseconds: `100`
作为一个普通数字,以毫秒为单位,如:`100`
* In a string, as milliseconds: `'100ms'`
作为一个字符串,以毫秒为单位,如:`'100ms'`
* In a string, as seconds: `'0.1s'`
作为一个字符串,以秒为单位,如:`'0.1s'`
### Delay
### 延迟
The delay controls the length of time between the animation trigger and the beginning
of the transition. You can define one by adding it to the same string
following the duration. It also has the same format options as the duration:
延迟控制的是在动画已经触发但尚未真正开始转场之前要等待多久。可以把它添加到字符串中的持续时间后面,它的选项格式也跟持续时间是一样的:
* Wait for 100ms and then run for 200ms: `'0.2s 100ms'`
等待100毫秒然后运行200毫秒`'0.2s 100ms'`。
### Easing
### 缓动函数
The [easing function](http://easings.net/) controls how the animation accelerates
and decelerates during its runtime. For example, an `ease-in` function causes
the animation to begin relatively slowly but pick up speed as it progresses. You
can control the easing by adding it as a *third* value in the string after the duration
and the delay (or as the *second* value when there is no delay):
[缓动函数](http://easings.net/)用于控制动画在运行期间如何加速和减速。比如:使用`ease-in`函数意味着动画开始时相对缓慢,然后在进行中逐步加速。可以通过在这个字符串中的持续时间和延迟后面添加*第三个*值来控制使用哪个缓动函数(如果没有定义延迟就作为*第二个*值)。
* Wait for 100ms and then run for 200ms, with easing: `'0.2s 100ms ease-out'`
等待100毫秒然后运行200毫秒并且带缓动`'0.2s 100ms ease-out'`
* Run for 200ms, with easing: `'0.2s ease-in-out'`
运行200毫秒并且带缓动`'0.2s ease-in-out'`
<figure>
<img src="assets/images/devguide/animations/animation_timings.gif" alt="Animations with specific timings" align="right" style="width:220px;margin-left:20px"></img>
@ -412,11 +598,15 @@ and the delay (or as the *second* value when there is no delay):
### Example
### 例子
Here are a couple of custom timings in action. Both enter and leave last for
200 milliseconds, that is `0.2s`, but they have different easings. The leave begins after a
slight delay of 10 milliseconds as specified in `'0.2s 10 ease-out'`:
这里是两个自定义时间线的动态演示。“进场”和“离场”都持续200毫秒也就是`0.2s`但它们有不同的缓动函数。“离场”动画会在100毫秒的延迟之后开始也就是`'0.2s 10 ease-out'`
<code-example path="animations/src/app/hero-list-timings.component.ts" region="animationdef" title="hero-list-timings.component.ts (excerpt)" linenums="false">
@ -426,6 +616,9 @@ slight delay of 10 milliseconds as specified in `'0.2s 10 ease-out'`:
## Multi-step animations with keyframes
## 基于关键帧(Keyframes)的多阶段动画
<figure>
<img src="assets/images/devguide/animations/animation_multistep.gif" alt="Animations with some bounce implemented with keyframes" align="right" style="width:220px;margin-left:20px"></img>
</figure>
@ -435,13 +628,19 @@ slight delay of 10 milliseconds as specified in `'0.2s 10 ease-out'`:
Animation *keyframes* go beyond a simple transition to a more intricate animation
that goes through one or more intermediate styles when transitioning between two sets of styles.
通过定义动画的*关键帧*,可以把两组样式之间的简单转场,升级成一种更复杂的动画,它会在转场期间经历一个或多个中间样式。
For each keyframe, you specify an *offset* that defines at which point
in the animation that keyframe applies. The offset is a number between zero,
which marks the beginning of the animation, and one, which marks the end.
每个关键帧都可以被指定一个*偏移量*用来定义该关键帧将被用在动画期间的哪个时间点。偏移量是一个介于0(表示动画起点)和1(表示动画终点)之间的数组。
This example adds some "bounce" to the enter and leave animations with
keyframes:
在这个例子中,我们使用关键帧来为进场和离场动画添加一些“反弹效果”:
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="animationdef" title="hero-list-multistep.component.ts (excerpt)" linenums="false">
@ -453,13 +652,19 @@ Note that the offsets are *not* defined in terms of absolute time. They are rela
measures from zero to one. The final timeline of the animation is based on the combination
of keyframe offsets, duration, delay, and easing.
注意,这个偏移量并*不是*用绝对数字定义的时间段而是在0到1之间的相对值百分比。动画的最终时间线会基于关键帧的偏移量、持续时间、延迟和缓动函数计算出来。
Defining offsets for keyframes is optional. If you omit them, offsets with even
spacing are automatically assigned. For example, three keyframes without predefined
offsets receive offsets `0`, `0.5`, and `1`.
为关键帧定义偏移量是可选的。如果省略它们,偏移量会自动根据帧数平均分布出来。例如,三个未定义过偏移量的关键帧会分别获得偏移量:`0`、`0.5`和`1`。
## Parallel animation groups
## 并行动画组(Group)
<figure>
<img src="assets/images/devguide/animations/animation_groups.gif" alt="Parallel animations with different timings, implemented with groups" align="right" style="width:220px;margin-left:20px"></img>
</figure>
@ -469,14 +674,21 @@ offsets receive offsets `0`, `0.5`, and `1`.
You've seen how to animate multiple style properties at the same time:
just put all of them into the same `style()` definition.
我们已经知道该如何在同一时间段进行多个样式的动画了:只要把它们都放进同一个`style()`定义中就行了!
But you may also want to configure different *timings* for animations that happen
in parallel. For example, you may want to animate two CSS properties but use a
different easing function for each one.
但我们也可能会希望为同时发生的几个动画配置不同的*时间线*。比如同时对两个CSS属性做动画但又得为它们定义不同的缓动函数。
For this you can use animation *groups*. In this example, using groups both on
enter and leave allows for two different timing configurations. Both
are applied to the same element in parallel, but run independently of each other:
这种情况下就可以用动画*组*来解决了。在这个例子中,我们同时在进场和离场时使用了组,以便能让它们使用两种不同的时间线配置。
它们被同时应用到同一个元素上,但又彼此独立运行:
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" title="hero-list-groups.component.ts (excerpt)" linenums="false">
@ -486,14 +698,21 @@ are applied to the same element in parallel, but run independently of each other
One group animates the element transform and width; the other group animates the opacity.
其中一个动画组对元素的`transform`和`width`做动画,另一个组则对`opacity`做动画。
## Animation callbacks
## 动画回调
A callback is fired when an animation is started and also when it is done.
当动画开始和结束时,会触发一个回调。
In the keyframes example, you have a `trigger` called `@flyInOut`. You can hook
those callbacks like this:
对于例子中的这个关键帧,我们有一个叫做`@flyInOut`的`trigger`。在那里我们可以挂钩到那些回调,比如:
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="template" title="hero-list-multistep.component.ts (excerpt)" linenums="false">
@ -504,4 +723,8 @@ those callbacks like this:
The callbacks receive an `AnimationEvent` that contains contains useful properties such as
`fromState`, `toState` and `totalTime`.
Those callbacks will fire whether or not an animation is picked up.
这些回调接收一个`AnimationTransitionEvent`参数,它包含一些有用的属性,例如`fromState``toState`和`totalTime`。
Those callbacks will fire whether or not an animation is picked up.
无论动画是否实际执行过,那些回调都会触发。

View File

@ -1,8 +1,8 @@
@title
Ahead-of-Time Compilation
预 (AoT) 编译器
@intro
Learn how to use ahead-of-time compilation.
学习如何使用预编译器
@description
@ -10,36 +10,89 @@ Learn how to use ahead-of-time compilation.
This cookbook describes how to radically improve performance by compiling _ahead-of-time_ (AOT)
during a build process.
这个烹饪指南描述如何通过在构建过程中进行预编译Ahead of Time - AOT来从根本上提升性能。
{@a toc}
# Contents
* [Overview](guide/overview)
# 目录
* [Overview](guide/aot-compiler#overview)
[概览](guide/aot-compiler#overview)
* [Ahead-of-time (AOT) vs just-in-time (JIT)](guide/aot-compiler#aot-jit)
[预编译(AOT) vs 即时编译(JIT)](guide/aot-compiler#aot-jit)
* [Why do AOT compilation?](guide/aot-compiler#why-aot)
[为什么需要AOT编译](guide/aot-compiler#why-aot)
* [Compile with AOT](guide/aot-compiler#compile)
[用AOT进行编译](guide/aot-compiler#compile)
* [Bootstrap](guide/aot-compiler#bootstrap)
[引导](guide/aot-compiler#bootstrap)
* [Tree shaking](guide/aot-compiler#tree-shaking)
*[Rollup](guide/aot-compiler#rollup)
*[Rollup Plugins](guide/aot-compiler#rollup-plugins)
*[Run Rollup](guide/aot-compiler#run-rollup)
[摇树优化Tree Shaking](guide/aot-compiler#tree-shaking)
* [Rollup](guide/aot-compiler#rollup)
* [Rollup Plugins](guide/aot-compiler#rollup-plugins)
[Rollup插件](guide/aot-compiler#rollup-plugins)
* [Run Rollup](guide/aot-compiler#run-rollup)
[运行Rollup](guide/aot-compiler#run-rollup)
* [Load the bundle](guide/aot-compiler#load)
[加载捆(bundle)文件](guide/aot-compiler#load)
* [Serve the app](guide/aot-compiler#serve)
[启动开发服务器](guide/aot-compiler#serve)
* [AOT QuickStart source code](guide/aot-compiler#source-code)
[AOT 快速起步源码](guide/aot-compiler#source-code)
* [Workflow and convenience script](guide/aot-compiler#workflow)
*[Develop JIT along with AOT](guide/aot-compiler#run-jit)
[工作流与辅助脚本](guide/aot-compiler#workflow)
* [Develop JIT along with AOT](guide/aot-compiler#run-jit)
[先用JIT开发再用AOT发布](guide/aot-compiler#run-jit)
* [Tour of Heroes](guide/aot-compiler#toh)
*[JIT in development, AOT in production](guide/aot-compiler#jit-dev-aot-prod)
*[Tree shaking](guide/aot-compiler#shaking)
*[Running the application](guide/aot-compiler#running-app)
*[Inspect the Bundle](guide/aot-compiler#inspect-bundle)
[英雄指南](guide/aot-compiler#toh)
* [JIT in development, AOT in production](guide/aot-compiler#jit-dev-aot-prod)
[开发环境JIT生产环境AOT](guide/aot-compiler#jit-dev-aot-prod)
* [Tree shaking](guide/aot-compiler#shaking)
[摇树优化](guide/aot-compiler#shaking)
* [Running the application](guide/aot-compiler#running-app)
[运行本应用](guide/aot-compiler#running-app)
* [Inspect the Bundle](guide/aot-compiler#inspect-bundle)
[审查捆(bundle)文件](guide/aot-compiler#inspect-bundle)
@ -49,16 +102,23 @@ during a build process.
## Overview
## 概览
An Angular application consists largely of components and their HTML templates.
Before the browser can render the application,
the components and templates must be converted to executable JavaScript by the _Angular compiler_.
Angular应用主要包含组件和它们的HTML模板。
在浏览器可以渲染应用之前,组件和模板必须要被**Angular编译器**转换为可以执行的JavaScript。
<div class="l-sub-section">
<a href="https://www.youtube.com/watch?v=kW9cJsvcsGo" target="_blank">Watch compiler author Tobias Bosch explain the Angular Compiler</a> at AngularConnect 2016.
观看编译器作者Tobias Bosch在AngularConnect 2016大会里<a href="http://v.youku.com/v_show/id_XMTc1NTE4NTkwOA==.html?from=y1.7-1.4" target="_blank">Angular编译器</a>的演讲。
</div>
@ -67,18 +127,30 @@ You can compile the app in the browser, at runtime, as the application loads, us
This is the standard development approach shown throughout the documentation.
It's great but it has shortcomings.
你可以在浏览器中使用*即时编译器*Just-in-Time - JIT在运行期间编译该应用也就是在应用加载时。
这是本文档中展示过的标准开发方式。
它很不错,但是有自己的缺点。
JIT compilation incurs a runtime performance penalty.
Views take longer to render because of the in-browser compilation step.
The application is bigger because it includes the Angular compiler
and a lot of library code that the application won't actually need.
Bigger apps take longer to transmit and are slower to load.
JIT编译导致运行期间的性能损耗。
由于需要在浏览器中执行这个编译过程,视图需要花更长时间才能渲染出来。
由于应用包含了Angular编译器以及大量实际上并不需要的库代码所以文件体积也会更大。
更大的应用需要更长的时间进行传输,加载也更慢。
Compilation can uncover many component-template binding errors.
JIT compilation discovers them at runtime, which is late in the process.
编译可以发现一些组件模板绑定错误。JIT编译在运行时才揭露它们那样有点太晚了。
The **_ahead-of-time_ (AOT) compiler** can catch template errors early and improve performance
by compiling at build time.
而**预编译**AOT会在构建时编译这样可以在早期截获模板错误提高应用性能。
{@a aot-jit}
@ -86,44 +158,73 @@ by compiling at build time.
## _Ahead-of-time_ (AOT) vs _just-in-time_ (JIT)
## 预编译AOT vs 即时编译JIT
There is actually only one Angular compiler. The difference between AOT and JIT is a matter of timing and tooling.
With AOT, the compiler runs once at build time using one set of libraries;
with JIT it runs every time for every user at runtime using a different set of libraries.
事实上只有一个Angular编译器AOT和JIT之间的差别仅仅在于编译的时机和所用的工具。
使用AOT编译器仅仅使用一组库在构建期间运行一次使用JIT编译器在每个用户的每次运行期间都要用不同的库运行一次。
{@a why-aot}
## Why do AOT compilation?
## 为什么需要AOT编译
*Faster rendering*
**渲染得更快**
With AOT, the browser downloads a pre-compiled version of the application.
The browser loads executable code so it can render the application immediately, without waiting to compile the app first.
使用AOT浏览器下载预编译版本的应用程序。
浏览器直接加载运行代码,所以它可以立即渲染该应用,而不用等应用完成首次编译。
*Fewer asynchronous requests*
**需要的异步请求更少**
The compiler _inlines_ external HTML templates and CSS style sheets within the application JavaScript,
eliminating separate ajax requests for those source files.
编译器把外部HTML模板和CSS样式表内联到了该应用的JavaScript中。
消除了用来下载那些源文件的Ajax请求。
*Smaller Angular framework download size*
**需要下载的Angular框架体积更小**
There's no need to download the Angular compiler if the app is already compiled.
The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload.
如果应用已经编译过了自然不需要再下载Angular编译器了。
该编译器差不多占了Angular自身体积的一半儿所以省略它可以显著减小应用的体积。
*Detect template errors earlier*
**提早检测模板错误**
The AOT compiler detects and reports template binding errors during the build step
before users can see them.
AOT编译器在构建过程中检测和报告模板绑定错误避免用户遇到这些错误。
*Better security*
**更安全**
AOT compiles HTML templates and components into JavaScript files long before they are served to the client.
With no templates to read and no risky client-side HTML or JavaScript evaluation,
there are fewer opportunities for injection attacks.
AOT编译远在HTML模版和组件被服务到客户端之前将它们编译到JavaScript文件。
没有模版可以阅读没有高风险客户端HTML或JavaScript可利用所以注入攻击的机会较少。
{@a compile}
@ -131,10 +232,15 @@ there are fewer opportunities for injection attacks.
## Compile with AOT
## 用AOT进行编译
Preparing for offline compilation takes a few simple steps.
Take the <a href='../guide/setup.html'>Setup</a> as a starting point.
A few minor changes to the lone `app.component` lead to these two class and HTML files:
AOT编译需要一些简单的准备步骤。我们先从<a href='../guide/setup.html'>搭建本地开发环境</a>开始。
只要单独对`app.component`文件的类文件和HTML文件做少量修改就可以了。
<code-tabs>
@ -150,7 +256,10 @@ A few minor changes to the lone `app.component` lead to these two class and HTML
Install a few new npm dependencies with the following command:
Install a few new npm dependencies with the following command:
用下列命令安装少量新的npm依赖
<code-example language="none" class="code-shell">
npm install @angular/compiler-cli @angular/platform-server --save
@ -161,12 +270,19 @@ Install a few new npm dependencies with the following command:
You will run the `ngc` compiler provided in the `@angular/compiler-cli` npm package
instead of the TypeScript compiler (`tsc`).
你要用`@angular/compiler-cli`包中提供的`ngc`编译器来代替TypeScript编译器`tsc`)。
`ngc` is a drop-in replacement for `tsc` and is configured much the same way.
`ngc`是一个`tsc`的高仿替代品,它们的配置方式几乎完全一样。
`ngc` requires its own `tsconfig.json` with AOT-oriented settings.
Copy the original `src/tsconfig.json` to a file called `tsconfig-aot.json` on the project root,
then modify it as follows.
`ngc`需要自己的带有AOT专用设置的`tsconfig.json`。
把原始的`tsconfig.json`拷贝到一个名叫`tsconfig-aot.json`的文件中,然后像这样修改它:
<code-example path="cb-aot-compiler/tsconfig-aot.json" title="tsconfig-aot.json" linenums="false">
@ -178,27 +294,49 @@ The `compilerOptions` section is unchanged except for one property.
**Set the `module` to `es2015`**.
This is important as explained later in the [Tree Shaking](guide/aot-compiler#tree-shaking) section.
`compilerOptions`部分只修改了一个属性:**把`module`设置为`es2015`。
这一点非常重要,我们会在后面的[摇树优化](guide/aot-compiler#tree-shaking)部分解释为什么。
What's really new is the `ngc` section at the bottom called `angularCompilerOptions`.
Its `genDir` property tells the compiler
to store the compiled output files in a new `aot` folder.
`ngc`区真正新增的内容是底部的`angularCompilerOptions`。
它的`genDir`属性告诉编译器把编译结果保存在新的`aot`目录下。
The `"skipMetadataEmit" : true` property prevents the compiler from generating metadata files with the compiled application.
Metadata files are not necessary when targeting TypeScript files, so there is no reason to include them.
`"skipMetadataEmit" : true`属性阻止编译器为编译后的应用生成元数据文件。
当输出成TypeScript文件时元数据并不是必须的因此不需要包含它们。
***Component-relative template URLS***
***相对于组件的模板URL***
The AOT compiler requires that `@Component` URLS for external templates and CSS files be _component-relative_.
That means that the value of `@Component.templateUrl` is a URL value _relative_ to the component class file.
For example, an `'app.component.html'` URL means that the template file is a sibling of its companion `app.component.ts` file.
AOT编译器要求`@Component`中的外部模板和CSS文件的URL是*相对于组件的*。
这意味着`@Component.templateUrl`的值是一个*相对于*组件类文件的URL值。
例如,`'app.component.html'` URL表示模板文件与它相应的`app.component.ts`文件放在一起。
While JIT app URLs are more flexible, stick with _component-relative_ URLs for compatibility with AOT compilation.
而JIT应用的URL更灵活固定写成*相对于组件的*URL的形式对AOT编译的兼容性也更好。
***Compiling the application***
*** 编译该应用 ***
Initiate AOT compilation from the command line using the previously installed `ngc` compiler by executing:
在命令行中执行下列命令,借助刚安装好的`ngc`编译器来启动AOT编译
<code-example language="none" class="code-shell">
node_modules/.bin/ngc -p tsconfig-aot.json
</code-example>
@ -211,6 +349,8 @@ Initiate AOT compilation from the command line using the previously installed `n
Windows users should surround the `ngc` command in double quotes:
Windows用户应该双引号`ngc`命令:
<code-example format='.'>
"node_modules/.bin/ngc" -p tsconfig-aot.json
</code-example>
@ -223,14 +363,23 @@ Windows users should surround the `ngc` command in double quotes:
`ngc` expects the `-p` switch to point to a `tsconfig.json` file or a folder containing a `tsconfig.json` file.
`ngc`希望`-p`选项指向一个`tsconfig.json`文件,或者一个包含`tsconfig.json`文件的目录。
After `ngc` completes, look for a collection of _NgFactory_ files in the `aot` folder.
The `aot` folder is the directory specified as `genDir` in `tsconfig-aot.json`.
在`ngc`完成时,会在`aot`目录下看到一组*NgFactory*文件(该目录是在`tsconfig-aot.json`的`genDir`属性中指定的)。
These factory files are essential to the compiled application.
Each component factory creates an instance of the component at runtime by combining the original class file
and a JavaScript representation of the component's template.
Note that the original component class is still referenced internally by the generated factory.
这些工厂文件对于编译后的应用是必要的。
每个组件工厂都可以在运行时创建一个组件的实例其中带有一个原始的类文件和一个用JavaScript表示的组件模板。
注意,原始的组件类依然是由所生成的这个工厂进行内部引用的。
<div class="l-sub-section">
@ -238,9 +387,14 @@ Note that the original component class is still referenced internally by the gen
The curious can open `aot/app.component.ngfactory.ts` to see the original Angular template syntax
compiled to TypeScript, its intermediate form.
如果你好奇,可以打开`aot/app.component.ngfactory.ts`来看看原始Angular模板语法被编译成TypeScript时的中间结果。
JIT compilation generates these same _NgFactories_ in memory where they are largely invisible.
AOT compilation reveals them as separate, physical files.
JIT编译器在内存中同样会生成这一堆*NgFactory*,但它们大部分是不可见的。
AOT编译器则会生成在单独的物理文件中。
</div>
@ -254,6 +408,8 @@ AOT compilation reveals them as separate, physical files.
Do not edit the _NgFactories_! Re-compilation replaces these files and all edits will be lost.
不要编辑这些*NgFactory*!重新编译时会替换这些文件,你做的所有修改都会丢失。
</div>
@ -265,19 +421,33 @@ Do not edit the _NgFactories_! Re-compilation replaces these files and all edits
## Bootstrap
## 引导
The AOT approach changes application bootstrapping.
AOT也改变了应用的引导方式。
Instead of bootstrapping `AppModule`, you bootstrap the application with the generated module factory, `AppModuleNgFactory`.
引导的方式从引导`AppModule`改成了引导生成的模块工厂:`AppModuleNgFactory`。
Make a copy of `main.ts` and name it `main-jit.ts`.
This is the JIT version; set it aside as you may need it [later](guide/aot-compiler#run-jit "Running with JIT").
复制一份`main.ts`并把它改名为`main-jit.ts`。
这就是JIT版本先把它放在一边我们[稍后](guide/aot-compiler#run-jit "Running with JIT")会用到它。
Open `main.ts` and convert it to AOT compilation.
Switch from the `platformBrowserDynamic.bootstrap` used in JIT compilation to
`platformBrowser().bootstrapModuleFactory` and pass in the AOT-generated `AppModuleNgFactory`.
打开`main.ts`并把它改成AOT编译。
从`platformBrowserDynamic.bootstrap`改成使用`platformBrowser().bootstrapModuleFactory`并把`AppModuleNgFactory`的AOT编译结果传给它。
Here is AOT bootstrap in `main.ts` next to the original JIT version:
这里是AOT版本`main.ts`中的引导过程下一个是你所熟悉的JIT版本。
<code-tabs>
@ -295,28 +465,44 @@ Here is AOT bootstrap in `main.ts` next to the original JIT version:
Be sure to [recompile](guide/aot-compiler#compiling-aot) with `ngc`!
确保用`ngc`进行[重新编译](guide/aot-compiler#compiling-aot)
{@a tree-shaking}
## Tree shaking
## 摇树优化Tree shaking
AOT compilation sets the stage for further optimization through a process called _tree shaking_.
A tree shaker walks the dependency graph, top to bottom, and _shakes out_ unused code like
dead leaves in a tree.
Tree shaking can greatly reduce the downloaded size of the application
by removing unused portions of both source and library code.
In fact, most of the reduction in small apps comes from removing unreferenced Angular features.
AOT编译为接下来通过一个叫做*摇树优化*的过程做好了准备。
摇树优化器从上到下遍历依赖图谱,并且*摇掉*用不到的代码,这些代码就像是圣诞树中那些死掉的松针一样。
Tree shaking can greatly reduce the downloaded size of the application
by removing unused portions of both source and library code.
In fact, most of the reduction in small apps comes from removing unreferenced Angular features.
通过移除源码和库代码中用不到的部分,摇树优化可以大幅缩减应用的下载体积。
事实上在小型应用中大部分的缩减都是因为筛掉了那些没用到的Angular特性。
For example, this demo application doesn't use anything from the `@angular/forms` library.
There is no reason to download forms-related Angular code and tree shaking ensures that you don't.
例如,这个演示程序中没有用到`@angular/forms`库中的任何东西那么也就没有理由去下载这些与表单有关的Angular代码了。摇树优化可以帮你确保这一点。
Tree shaking and AOT compilation are separate steps.
Tree shaking can only target JavaScript code.
AOT compilation converts more of the application to JavaScript,
which in turn makes more of the application "tree shakable".
摇树优化和AOT编译是单独的步骤。
摇树优化仅仅针对JavaScript代码。
AOT编译会把应用中的大部分都转换成JavaScript这种转换会让应用更容易被“摇树优化”。
{@a rollup}
@ -325,11 +511,19 @@ which in turn makes more of the application "tree shakable".
This cookbook illustrates a tree shaking utility called _Rollup_.
这个烹饪宝典中用来示范的摇树优化工具是*Rollup*。
Rollup statically analyzes the application by following the trail of `import` and `export` statements.
It produces a final code _bundle_ that excludes code that is exported, but never imported.
Rollup会通过跟踪`import`和`export`语句来对本应用进行静态分析。
它所生成的最终代码*捆*中会排除那些被导出过但又从未被导入的代码。
Rollup can only tree shake `ES2015` modules which have `import` and `export` statements.
Rollup只能对`ES2015`模块摇树,因为那里有`import`和`export`语句。
<div class="l-sub-section">
@ -338,12 +532,19 @@ Recall that `tsconfig-aot.json` is configured to produce `ES2015` modules.
It's not important that the code itself be written with `ES2015` syntax such as `class` and `const`.
What matters is that the code uses ES `import` and `export` statements rather than `require` statements.
回忆一下,`tsconfig-aot.json`中曾配置为生成`ES2015`的模块。
代码本身是否用到了`ES2015`语法(例如`class`和`const`)并不重要,重要的是这些代码使用的应该是`import`和`export`语句,而不是`require`语句。
</div>
In the terminal window, install the Rollup dependencies with this command:
通过下列命令安装Rollup依赖
<code-example language="none" class="code-shell">
npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-uglify --save-dev
</code-example>
@ -354,6 +555,9 @@ Next, create a configuration file (`rollup-config.js`)
in the project root directory to tell Rollup how to process the application.
The cookbook configuration file looks like this.
接下来,在项目根目录新建一个配置文件(`rollup-config.js`来告诉Rollup如何处理应用。
本烹饪书配置文件是这样的:
<code-example path="cb-aot-compiler/rollup-config.js" title="rollup-config.js" linenums="false">
@ -365,30 +569,50 @@ This config file tells Rollup that the app entry point is `src/app/main.js` .
The `dest` attribute tells Rollup to create a bundle called `build.js` in the `dist` folder.
It overrides the default `onwarn` method in order to skip annoying messages about the AOT compiler's use of the `this` keyword.
这个配置文件告诉Rollup该应用的入口点是`app/main.js`。
`dest`属性告诉Rollup要在`dist`目录下创建一个名叫`build.js`的捆文件。
它覆盖了默认的`onwarn`方法以便忽略由于AOT编译器使用`this`关键字导致的噪音消息。
The next section covers the plugins in more depth.
下一节我们将深入讲解插件。
{@a rollup-plugins}
### Rollup Plugins
### Rollup插件
Optional plugins filter and transform the Rollup inputs and output.
这些可选插件过滤并转换Rollup的输入和输出。
*RxJS*
Rollup expects application source code to use `ES2015` modules.
Rollup expects application source code to use `ES2015` modules.
Not all external dependencies are published as `ES2015` modules.
In fact, most are not. Many of them are published as _CommonJS_ modules.
Rollup期望应用的源码使用`ES2015`模块。
但并不是所有外部依赖都发布成了`ES2015`模块。
事实上,大多数都不是。它们大多数都发布成了*CommonJS*模块。
The _RxJs_ Observable library is an essential Angular dependency published as an ES5 JavaScript _CommonJS_ module.
可观察对象库*RxJS*是Angular所依赖的基础之一它就是发布成了ES5 JavaScript的*CommonJS*模块。
Luckily, there is a Rollup plugin that modifies _RxJs_
to use the ES `import` and `export` statements that Rollup requires.
Rollup then preserves the parts of `RxJS` referenced by the application
in the final bundle. Using it is straigthforward. Add the following to
the `plugins` array in `rollup-config.js`:
幸运的是有一个Rollup插件它会修改*RxJS*以使用Rollup所需的ES`import`和`export`语句。
然后Rollup就可以把该应用中用到的那部分`RxJS`代码留在“捆”文件中了。
它的用法很简单。把下列代码添加到`rollup-config.js`的`plugins`数组中:
<code-example path="cb-aot-compiler/rollup-config.js" region="commonjs" title="rollup-config.js (CommonJs to ES2015 Plugin)" linenums="false">
@ -398,10 +622,16 @@ the `plugins` array in `rollup-config.js`:
*Minification*
*最小化*
Rollup tree shaking reduces code size considerably. Minification makes it smaller still.
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
Add the following to the `plugins` array:
Rollup做摇树优化时会大幅减小代码体积。最小化过程则会让它更小。
本烹饪宝典依赖于Rollup插件*uglify*来最小化并混淆代码。
把下列代码添加到`plugins`数组中:
<code-example path="cb-aot-compiler/rollup-config.js" region="uglify" title="rollup-config.js (CommonJs to ES2015 Plugin)" linenums="false">
@ -416,6 +646,8 @@ Add the following to the `plugins` array:
In a production setting, you would also enable gzip on the web server to compress
the code into an even smaller package going over the wire.
在生产环境中我们还应该打开Web服务器的gzip特性来把代码压缩得更小。
</div>
@ -425,8 +657,14 @@ the code into an even smaller package going over the wire.
### Run Rollup
### 运行Rollup
Execute the Rollup process with this command:
通过下列命令执行Rollup过程
<code-example language="none" class="code-shell">
node_modules/.bin/rollup -c rollup-config.js
</code-example>
@ -439,6 +677,9 @@ Execute the Rollup process with this command:
Windows users should surround the `rollup` command in double quotes:
Windows用户要把`rollup`命令放进双引号中:
<code-example language="none" class="code-shell">
"node_modules/.bin/rollup" -c rollup-config.js
</code-example>
@ -457,10 +698,16 @@ Windows users should surround the `rollup` command in double quotes:
## Load the bundle
## 加载捆文件
Loading the generated application bundle does not require a module loader like SystemJS.
Remove the scripts that concern SystemJS.
Instead, load the bundle file using a single `<script>` tag **_after_** the `</body>` tag:
加载所生成的应用捆文件并不需要使用像SystemJS这样的模块加载器。
移除与SystemJS有关的那些脚本吧。
改用`<script>`
<code-example path="cb-aot-compiler/src/index.html" region="bundle" title="index.html (load bundle)" linenums="false">
@ -474,17 +721,26 @@ Instead, load the bundle file using a single `<script>` tag **_after_** the `</b
## Serve the app
## 启动应用服务器
You'll need a web server to host the application.
Use the same `lite-server` employed elsewhere in the documentation:
你需要一个Web服务器来作为应用的宿主。
像与文档中其它部分一样,用`lite-server`吧:
<code-example language="none" class="code-shell">
npm run lite
</code-example>
The server starts, launches a browser, and the app should appear.
启动了服务器、打开浏览器,应用就出现了。
{@a source-code}
@ -492,8 +748,12 @@ The server starts, launches a browser, and the app should appear.
## AOT QuickStart source code
## AOT快速起步源代码
Here's the pertinent source code:
下面是相关源代码:
<code-tabs>
<code-pane title="src/app/app.component.html" path="cb-aot-compiler/src/app/app.component.html">
@ -530,13 +790,24 @@ Here's the pertinent source code:
## Workflow and convenience script
## 工作流与便利脚本
You'll rebuild the AOT version of the application every time you make a change.
Those _npm_ commands are long and difficult to remember.
每当修改时我们都将重新构建应用的AOT版本。
那些*npm*命令太长,很难记。
Add the following _npm_ convenience script to the `package.json` so you can compile and rollup in one command.
把下列*npm*便利脚本添加到`package.json`中以便用一条命令就可以完成编译和Rollup打包工作。
Open a terminal window and try it.
打开终端窗口,并试一下。
<code-example language="none" class="code-shell">
npm run build:aot
@ -549,14 +820,29 @@ Open a terminal window and try it.
### Develop JIT along with AOT
### 先用JIT开发再AOT发布
AOT compilation and rollup together take several seconds.
You may be able to develop iteratively a little faster with SystemJS and JIT.
The same source code can be built both ways. Here's one way to do that.
AOT编译和Rollup打包加起来要花好几秒钟。
用SystemJS和JIT可以让开发期间的迭代更快一点。
同一套源码可以用这两种方式构建。下面是方法之一:
* Make a copy of `index.html` and call it `index-jit.html`.
复制一份`index.html`并命名为`index-jit.html`。
* Delete the script at the bottom of `index-jit.html` that loads `bundle.js`
删除`index-jit.html`底部用来加载`bundle.js`的脚本
* Restore the SystemJS scripts like this:
代之以如下的SystemJS脚本
<code-example path="cb-aot-compiler/src/index-jit.html" region="jit" title="src/index-jit.html (SystemJS scripts)" linenums="false">
</code-example>
@ -566,11 +852,18 @@ The same source code can be built both ways. Here's one way to do that.
Notice the slight change to the `system.import` which now specifies `src/app/main-jit`.
That's the JIT version of the bootstrap file that we preserved [above](guide/aot-compiler#bootstrap).
注意,这里稍微修改了一下`system.import`,现在它指向了`src/app/main-jit`。
这就是我们[以前](guide/aot-compiler#bootstrap)预留的JIT版本的引导文件。
Open a _different_ terminal window and enter `npm start`.
打开*另一个*终端窗口,并输入`npm start`
<code-example language="none" class="code-shell">
npm start
</code-example>
@ -580,15 +873,25 @@ The server loads `index.html` which is still the AOT version, which you can conf
Change the address bar to `index-jit.html` and it loads the JIT version.
This is also evident in the browser console.
它会使用JIT方式编译本应用并启动服务器。
服务器仍然加载的是AOT版的`index.html`,我们可以在浏览器的控制台中确认这一点。
在地址栏中改为`index-jit.html`它就会加载JIT版这同样可以在浏览器控制台中确认。
Develop as usual.
The server and TypeScript compiler are in "watch mode" so your changes are reflected immediately in the browser.
照常开发。服务器和TypeScript编译器都处于“监听模式”因此我们的修改都可以立刻反映到浏览器中。
To see those changes in AOT, switch to the original terminal and re-run `npm run build:aot`.
When it finishes, go back to the browser and use the back button to
return to the AOT version in the default `index.html`.
要对比AOT版的变化可以切换到原来的终端窗口中并重新运行`npm run build:aot`。
结束时,回到浏览器中,并用浏览器的后退按钮回到默认`index.html`中的AOT版本。
Now you can develop JIT and AOT, side-by-side.
现在我们就可以同时进行JIT和AOT开发了。
{@a toh}
@ -597,27 +900,43 @@ Now you can develop JIT and AOT, side-by-side.
## Tour of Heroes
## 英雄指南
The sample above is a trivial variation of the QuickStart application.
In this section you apply what you've learned about AOT compilation and tree shaking
to an app with more substance, the [_Tour of Heroes_](tutorial/toh-pt6) application.
上面的例子是《快速起步》应用的一个简单的变体。
在本节中,你将在一个更多内容的应用 - [英雄指南](tutorial/toh-pt6)上使用从AOT编译和摇树优化学到的知识。
{@a jit-dev-aot-prod}
### JIT in development, AOT in production
### 开发器使用JIT, 产品期使用AOT
Today AOT compilation and tree shaking take more time than is practical for development. That will change soon.
For now, it's best to JIT compile in development and switch to AOT compilation before deploying to production.
目前AOT编译和摇树优化对开发来说占用的时间太多了。这将在未来得到改变。
当前的最佳实践是在开发器使用JIT编译然后在发布产品前切换到AOT编译。
Fortunately, the source code can be compiled either way without change _if_ you account for a few key differences.
幸运的是,**如果**你处理了几个关键不同点,源代码可以在没有任何变化时,采取两种方式的任何一种都能编译。
***index.html***
The JIT and AOT apps require their own `index.html` files because they setup and launch so differently.
The JIT and AOT apps require their own `index.html` files because they setup and launch so differently.
JIT和AOT应用的设置和加载非常不一样因此它们需要各自的`index.html`文件。
Here they are for comparison:
下面是它们的比较:
<code-tabs>
@ -636,6 +955,9 @@ Here they are for comparison:
The JIT version relies on `SystemJS` to load individual modules.
Its scripts appear in its `index.html`.
JIT版本依靠`SystemJS`来加载单个模块,并需要`reflect-metadata`垫片。
所以它们出现在它的`index.html`中。
The AOT version loads the entire application in a single script, `aot/dist/build.js`.
It does not need `SystemJS`, so that script is absent from its `index.html`
@ -645,6 +967,8 @@ JIT and AOT applications boot in much the same way but require different Angular
The key differences, covered in the [Bootstrap](guide/aot-compiler#bootstrap) section above,
are evident in these `main` files which can and should reside in the same folder:
AOT版本用一个单独的脚本来加载整个应用 - `aot/dist/build.js`。它不需要`SystemJS`和`reflect-metadata`垫片,所以它们不会出现在`index.html`中。
<code-tabs>
@ -662,12 +986,20 @@ are evident in these `main` files which can and should reside in the same folder
***TypeScript configuration***
***TypeScript配置***
JIT-compiled applications transpile to `commonjs` modules.
AOT-compiled applications transpile to _ES2015_/_ES6_ modules to facilitate tree shaking.
AOT requires its own TypeScript configuration settings as well.
JIT编译的应用编译为`commonjs`模块。
AOT编译的应用编译为**ES2015/ES6**模块,用来支持摇树优化。
而且AOT需要它自己的TypeScript配置设置。
You'll need separate TypeScript configuration files such as these:
你将需要单独的TypeScript配置文件像这些
<code-tabs>
@ -693,14 +1025,28 @@ You'll need separate TypeScript configuration files such as these:
<header>
@Types和node模块
</header>
In the file structure of _this particular sample project_,
the `node_modules` folder happens to be two levels up from the project root.
Therefore, `"typeRoots"` must be set to `"../../node_modules/@types/"`.
the `node_modules` folder happens to be two levels up from the project root.
Therefore, `"typeRoots"` must be set to `"../../node_modules/@types/"`.
在**这个特定的示例项目**的文件结构中,`node_modules`文件恰好比项目根目录高两级。
因此,`"typeRoots"`必须设置为`"../../node_modules/@types/"`。
In a more typical project, `node_modules` would be a sibling of `tsconfig-aot.json`
and `"typeRoots"` would be set to `"node_modules/@types/"`.
Edit your `tsconfig-aot.json` to fit your project's file structure.
在一个更典型的项目中,`node_modules`位于`tsconfig-aot.json`同级,
这时`"typeRoots"`应设置为`"node_modules/@types/"`。
编辑你的`tsconfig-aot.json`,使之适合项目的文件结构。
</div>
@ -710,8 +1056,12 @@ Edit your `tsconfig-aot.json` to fit your project's file structure.
### Tree shaking
### 摇树优化
Rollup does the tree shaking as before.
Rollup和以前一样仍然进行摇树优化。
<code-example path="toh-6/rollup-config.js" title="rollup-config.js" linenums="false">
@ -724,6 +1074,8 @@ Rollup does the tree shaking as before.
### Running the application
### 运行应用
<div class="alert is-important">
@ -731,20 +1083,31 @@ Rollup does the tree shaking as before.
The general audience instructions for running the AOT build of the Tour of Heroes app are not ready.
面向大众的运行AOT构建的英雄指南应用的说明还没有准备好。
The following instructions presuppose that you have cloned the
<a href="https://github.com/angular/angular.io" target="_blank">angular.io</a>
github repository and prepared it for development as explained in the repo's README.md.
下面的说明假设你克隆了<a href="https://github.com/angular/angular.io" target="_blank">angular.io</a> Github库并按照该库的README.md准备了开发环境。
The _Tour of Heroes_ source code is in the `public/docs/_examples/toh-6/ts` folder.
**英雄指南**源代码在`public/docs/_examples/toh-6/ts`目录。
</div>
Run the JIT-compiled app with `npm start` as for all other JIT examples.
和其他JIT例子一样使用`npm start`命令运行JIT编译的应用
Compiling with AOT presupposes certain supporting files, most of them discussed above.
AOT编译假设上面介绍的一些支持文件都以准备好。
<code-tabs>
<code-pane title="src/index.html" path="toh-6/src/index.html">
@ -769,8 +1132,13 @@ Compiling with AOT presupposes certain supporting files, most of them discussed
Extend the `scripts` section of the `package.json` with these npm scripts:
使用下面的npm脚本扩展`package.json`文件的`scripts`部分:
Copy the AOT distribution files into the `/aot` folder with the node script:
使用下面的node脚本拷贝AOT发布文件到`/aot/`目录:
<code-example language="none" class="code-shell">
node copy-dist-files
</code-example>
@ -783,12 +1151,17 @@ Copy the AOT distribution files into the `/aot` folder with the node script:
You won't do that again until there are updates to `zone.js` or the `core-js` shim for old browsers.
直到`zone.js`或者支持老版本浏览器的`core-js`垫片有更新,你不需要再这样做。
</div>
Now AOT-compile the app and launch it with the `lite-server`:
现在AOT编译应用并使用`lite`服务器启动它:
<code-example language="none" class="code-shell">
npm run build:aot && npm run serve:aot
@ -801,13 +1174,21 @@ Now AOT-compile the app and launch it with the `lite-server`:
### Inspect the Bundle
### 检查包裹
It's fascinating to see what the generated JavaScript bundle looks like after Rollup.
The code is minified, so you won't learn much from inspecting the bundle directly.
But the <a href="https://github.com/danvk/source-map-explorer/blob/master/README.md" target="_blank">source-map-explorer</a>
tool can be quite revealing.
看看Rollup之后生成的JavaScript包非常神奇。
代码已经被最小化,所以你不会从中直接学到任何知识。
但是<a href="https://github.com/danvk/source-map-explorer/blob/master/README.md" target="_blank">source-map-explorer</a> 工具非常有用。
Install it:
安装:
<code-example language="none" class="code-shell">
npm install source-map-explorer --save-dev
</code-example>
@ -816,6 +1197,8 @@ Install it:
Run the following command to generate the map.
运行下面的命令来生成源映射。
<code-example language="none" class="code-shell">
node_modules/.bin/source-map-explorer aot/dist/build.js
@ -827,7 +1210,11 @@ Run the following command to generate the map.
The `source-map-explorer` analyzes the source map generated with the bundle and draws a map of all dependencies,
showing exactly which application and Angular modules and classes are included in the bundle.
`source-map-explorer`分析从包生成的源映射并画出一个依赖地图显示包中包含哪些应用程序和Angular模块和类。
Here's the map for _Tour of Heroes_.
下面是英雄指南的地图:
<a href="assets/images/cookbooks/aot-compiler/toh6-bundle.png" target="_blank" title="View larger image">
<figure class='image-display'>
<img src="assets/images/cookbooks/aot-compiler/toh6-bundle.png" alt="TOH-6-bundle"></img>

View File

@ -1,8 +1,8 @@
@title
AppModule: the root module
AppModule: 根模块
@intro
Tell Angular how to construct and bootstrap the app in the root "AppModule".
如何在根 "AppModule" 中构建和启动应用。
@description
@ -12,9 +12,16 @@ Every application has at least one Angular module, the _root_ module
that you [bootstrap](guide/appmodule#main) to launch the application.
You can call it anything you want. The conventional name is `AppModule`.
Angular 模块类描述应用的部件是如何组合在一起的。
每个应用都至少有一个 Angular 模块,也就是*根*模块,用来[引导](guide/appmodule#main)并运行应用。
你可以为它取任何名字。常规名字是`AppModule`。
The [setup](guide/setup) instructions produce a new project with the following minimal `AppModule`.
You'll evolve this module as your application grows.
[开发环境](guide/setup)讲解了如何使用下面这个最小的`AppModule`来创建一个新项目。
这个模块随着应用的成长而演变。
<code-example path="setup/src/app/app.module.ts" title="src/app/app.module.ts" linenums="false">
@ -25,15 +32,31 @@ You'll evolve this module as your application grows.
After the `import` statements, you come to a class adorned with the
**`@NgModule`** [_decorator_](guide/glossary#decorator '"Decorator" explained').
`import`语句之后,可以看到一个**`@NgModule`**[装饰器](guide/glossary#decorator '"Decorator" explained')修饰的类。
The `@NgModule` decorator identifies `AppModule` as an Angular module class (also called an `NgModule` class).
`@NgModule` takes a _metadata_ object that tells Angular how to compile and launch the application.
`@NgModule`装饰器将`AppModule`标记为 Angular 模块类(也叫`NgModule`类)。
`@NgModule`接受一个_元数据_对象告诉 Angular 如何编译和启动应用。
* **_imports_** &mdash; the `BrowserModule` that this and every application needs to run in a browser.
**_imports_** &mdash; `BrowserModule`,这个和每个在浏览器中运行应用都需要它。
* **_declarations_** &mdash; the application's lone component, which is also ...
**_declarations_** &mdash; 应用的唯一组件,它同时也是...
* **_bootstrap_** &mdash; the _root_ component that Angular creates and inserts into the `index.html` host web page.
**_bootstrap_** &mdash; _根_组件Angular 创建它并插入`index.html`宿主页面。
The [Angular Modules (NgModule)](guide/ngmodule) guide dives deeply into the details of Angular modules.
All you need to know at the moment is a few basics about these three properties.
All you need to know at the moment is a few basics about these three properties.
[Angular 模块 (NgModules)](guide/ngmodule) 指南深入讲解了 Angular 模块。
现在先初步了解这三个属性。
{@a imports}
@ -41,18 +64,32 @@ All you need to know at the moment is a few basics about these three properties.
### The _imports_ array
### _imports_ 数组
Angular modules are a way to consolidate features that belong together into discrete units.
Many features of Angular itself are organized as Angular modules.
HTTP services are in the `HttpModule`. The router is in the `RouterModule`.
Eventually you may create a feature module.
Angular 模块把特性合并成离散单元的一种方式。
Angular 自身的许多特性也是通过 Angular 模块组织的。
HTTP 服务在`HttpModule`里。路由器在`RouterModule`中。
最终,你可能也会创建特性模块。
Add a module to the `imports` array when the application requires its features.
当应用需要模块的特性时,将其添加到`imports`数组中。
_This_ application, like most applications, executes in a browser.
Every application that executes in a browser needs the `BrowserModule` from `@angular/platform-browser`.
So every such application includes the `BrowserModule` in its _root_ `AppModule`'s `imports` array.
Other guide and cookbook pages will tell you when you need to add additional modules to this array.
_这个_应用和大多数其他应用一样在浏览器中运行。
每个浏览器中运行的应用都需要`@angular/platform-browser`里的`BrowserModule`。
所以每个这样的应用都在其_根_`AppModule`的`imports`数组中包含`BrowserModule`。
在需要添加额外模块到此数组时,其他指南和烹饪宝典页面会告诉你。
<div class="alert is-important">
@ -60,6 +97,8 @@ Other guide and cookbook pages will tell you when you need to add additional mod
**Only `NgModule` classes** go in the `imports` array. Do not put any other kind of class in `imports`.
`imports`数组中应该**只有`NgModule`类**。不要放置其它类型的类。
</div>
@ -72,15 +111,23 @@ Other guide and cookbook pages will tell you when you need to add additional mod
The `import` statements at the top of the file and the Angular module's `imports` array
are unrelated and have completely different jobs.
不要将 Angular 模块的`imports`数组与文件顶部的`import`语句弄混淆了。它们的功能不同。
The _JavaScript_ `import` statements give you access to symbols _exported_ by other files
so you can reference them within _this_ file.
You add `import` statements to almost every application file.
They have nothing to do with Angular and Angular knows nothing about them.
_JavaScript_ 的`import`声明允许你访问在其他文件中_导出_的符号这样你可以在_当前_文件引用它们。
我们会往几乎所有类型的应用中加入`import`语句。
它们与 Angular 毫无关系Angular 对它们也一无所知。
The _module's_ `imports` array appears _exclusively_ in the `@NgModule` metadata object.
It tells Angular about specific _other_ Angular modules &mdash; all of them classes decorated with `@NgModule` &mdash;
that the application needs to function properly.
_模块_的`imports`数组是`@NgModule`元数据中*独有的*。它告诉 Angular 特定 Angular 模块的信息 &mdash; 用`@NgModule`装饰的类 &mdash; 应用需要它们来正常工作。
</div>
@ -90,16 +137,27 @@ that the application needs to function properly.
### The _declarations_ array
### *declarations* 数组
You tell Angular which components belong to the `AppModule` by listing it in the module's `declarations` array.
As you create more components, you'll add them to `declarations`.
通过将其列到`AppModule`模块的`declarations`数组中,可以告诉 Angular 哪个组件属于`AppModule`。
在创建更多组件的过程中,逐步将它们添加到`declarations`中。
You must declare _every_ component in an `NgModule` class.
If you use a component without declaring it, you'll see a clear error message in the browser console.
你必须在一个`NgModule`类声明*每一个*组件。
否则当你使用这些组件时就会在浏览器的控制台中看到一个明显的错误信息。
You'll learn to create two other kinds of classes &mdash;
[directives](guide/attribute-directives) and [pipes](guide/pipes) &mdash;
that you must also add to the `declarations` array.
你将会学习如何创建其他两种类 &mdash; [指令](guide/attribute-directives)和[管道](guide/pipes) &mdash;
它们也必须被添加到`declarations`数组。
<div class="alert is-important">
@ -108,6 +166,9 @@ that you must also add to the `declarations` array.
**Only _declarables_** &mdash; _components_, _directives_ and _pipes_ &mdash; belong in the `declarations` array.
Do not put any other kind of class in `declarations`; _not_ `NgModule` classes, _not_ service classes, _not_ model classes.
**只有*可以声明的** &mdash; _组件_、_指令_和_管道_ &mdash; 属于`declarations`数组。
不要将其他类型的类添加到`declarations`中,例如`NgModule`类, 服务类,模型类。
</div>
@ -118,20 +179,36 @@ Do not put any other kind of class in `declarations`; _not_ `NgModule` classes,
### The _bootstrap_ array
### _bootstrap_ 数组
You launch the application by [_bootstrapping_](guide/appmodule#main) the root `AppModule`.
Among other things, the _bootstrapping_ process creates the component(s) listed in the `bootstrap` array
and inserts each one into the browser DOM.
通过[_引导_](guide/appmodule#main)根`AppModule`来启动应用。
在启动过程中,其中一步是创建列在`bootstrap`数组的组件,
并将它们每一个都插入到浏览器的DOM中。
Each bootstrapped component is the base of its own tree of components.
Inserting a bootstrapped component usually triggers a cascade of component creations that fill out that tree.
每个被引导的组件都是它自己的组件树的根。
插入一个被引导的组件通常触发一系列组件的创建并形成组件树。
While you can put more than one component tree on a host web page, that's not typical.
Most applications have only one component tree and they bootstrap a single _root_ component.
虽然你可以将多个组件树插入到宿主页面,但并不普遍。
大多数应用只有一个组件树它们引导单一_根_组件。
You can call the one _root_ component anything you want but most developers call it `AppComponent`.
你可以为这个_根_组件取任何名字但是大多数程序员将其取名为`AppComponent`。
Which brings us to the _bootstrapping_ process itself.
下面让我们来看看*引导*过程本身。
{@a main}
@ -144,15 +221,26 @@ Which brings us to the _bootstrapping_ process itself.
## Bootstrap in _main.ts_
## 在*main.ts*中引导
There are many ways to bootstrap an application.
The variations depend upon how you want to compile the application and where you want to run it.
引导应用的方法很多。
它们取决于你想如何编译应用以及应用将在哪儿运行。
In the beginning, you will compile the application dynamically with the _Just-in-Time (JIT)_ compiler
and you'll run it in a browser. You can learn about other options later.
开始时你将使用_即时 (JiT) _编译器动态编译应用。然后在浏览器中运行它。
稍后,你可以了解其他选项。
The recommended place to bootstrap a JIT-compiled browser application is in a separate file
in the `src` folder named `src/main.ts`
引导即时编译的浏览器应用的推荐地点是在`src`目录中一个名为`src/main.ts`的单独文件中。
<code-example path="setup/src/main.ts" title="src/main.ts" linenums="false">
</code-example>
@ -162,13 +250,20 @@ in the `src` folder named `src/main.ts`
This code creates a browser platform for dynamic (JIT) compilation and
bootstraps the `AppModule` described above.
上面的代码为动态 (JiT) 编译创建浏览器平台,并引导上面提到的`AppModule`。
The _bootstrapping_ process sets up the execution environment,
digs the _root_ `AppComponent` out of the module's `bootstrap` array,
creates an instance of the component and inserts it within the element tag identified by the component's `selector`.
引导过程搭建运行环境,从模块的`bootstrap`数组中提出_根_`AppComponent` 创建该组件的实例,并将其插入到组件`selector`标识的元素标签中。
The `AppComponent` selector &mdash; here and in most documentation samples &mdash; is `my-app`
so Angular looks for a `<my-app>` tag in the `index.html` like this one ...
`AppComponent`选择器 &mdash; 在这里以及文档大部分例子中 &mdash; 是`my-app`
所以 Angular 在`index.html`中查找像这样的`<my-app>`标签...
<code-example path="setup/src/index.html" region="my-app" title="setup/src/index.html" linenums="false">
</code-example>
@ -177,8 +272,12 @@ so Angular looks for a `<my-app>` tag in the `index.html` like this one ...
... and displays the `AppComponent` there.
...然后在那儿显示`AppComponent`。
This file is very stable. Once you've set it up, you may never change it again.
该文件非常稳定。一旦配置好,你可能永远不会再修改它。
<l-main-section>
@ -188,9 +287,17 @@ This file is very stable. Once you've set it up, you may never change it again.
## More about Angular Modules
## 关于Angular模块的更多知识
Your initial app has only a single module, the _root_ module.
As your app grows, you'll consider subdividing it into multiple "feature" modules,
some of which can be loaded later ("lazy loaded") if and when the user chooses
to visit those features.
When you're ready to explore these possibilities, visit the [Angular Modules (NgModule)](guide/ngmodule) guide.
你最初的应用只有一个单一的模块 —— *根*模块。
当这个应用不断成长时,你就要考虑把它们拆分到多个 "特性" 模块中了。
这些特性模块中的一部分可以稍后加载(即惰性加载),它们只会在用户访问到这些特性时才会加载。
When you're ready to explore these possibilities, visit the [Angular Modules (NgModule)](guide/ngmodule) guide.
如果你要了解这些知识,请访问[Angular 模块 (NgModule)](guide/ngmodule)页

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
@title
Attribute Directives
属性型指令
@intro
Attribute directives attach behavior to elements.
属性型指令把行为添加到现有元素上。
@description
@ -10,52 +10,103 @@ Attribute directives attach behavior to elements.
An **Attribute** directive changes the appearance or behavior of a DOM element.
**属性**型指令用于改变一个 DOM 元素的外观或行为。
# Contents
# 目录
* [Directives overview](guide/attribute-directives#directive-overview)
[指令概览](guide/attribute-directives#directive-overview)
* [Build a simple attribute directive](guide/attribute-directives#write-directive)
[创建简单的属性型指令](guide/attribute-directives#write-directive)
* [Apply the attribute directive to an element in a template](guide/attribute-directives#apply-directive)
[应用属性型指令到模板中的元素](guide/attribute-directives#apply-directive)
* [Respond to user-initiated events](guide/attribute-directives#respond-to-user)
[响应用户引发的事件](guide/attribute-directives#respond-to-user)
* [Pass values into the directive with an _@Input_ data binding](guide/attribute-directives#bindings)
[使用数据绑定把值传到指令中](guide/attribute-directives#bindings)
* [Bind to a second property](guide/attribute-directives#second-property)
[绑定第二个属性](guide/attribute-directives#second-property)
Try the <live-example title="Attribute Directive example"></live-example>.
试试<live-example title="Attribute Directive example"></live-example>
## Directives overview
## 指令概览
There are three kinds of directives in Angular:
在 Angular 中有三种类型的指令:
1. Components&mdash;directives with a template.
组件 &mdash; 拥有模板的指令
1. Structural directives&mdash;change the DOM layout by adding and removing DOM elements.
结构型指令 &mdash; 通过添加和移除 DOM 元素改变 DOM 布局的指令
1. Attribute directives&mdash;change the appearance or behavior of an element, component, or another directive.
属性型指令 &mdash; 改变元素、组件或其它指令的外观和行为的指令。
*Components* are the most common of the three directives.
You saw a component for the first time in the [QuickStart](quickstart) guide.
*组件*是这三种指令中最常用的。
你在[快速起步](quickstart#root-component)例子中第一次见到组件。
*Structural Directives* change the structure of the view.
Two examples are [NgFor](guide/template-syntax#ngFor) and [NgIf](guide/template-syntax#ngIf).
Learn about them in the [Structural Directives](guide/structural-directives) guide.
*结构型*指令修改视图的结构。例如,[NgFor](guide/template-syntax#ngFor) 和 [NgIf](guide/template-syntax#ngIf)。
要了解更多,参见[结构型指令](guide/structural-directives) guide。
*Attribute directives* are used as attributes of elements.
The built-in [NgStyle](guide/template-syntax#ngStyle) directive in the
[Template Syntax](guide/template-syntax) guide, for example,
can change several element styles at the same time.
*属性型*指令改变一个元素的外观或行为。例如,内置的 [NgStyle](guide/template-syntax#ngStyle) 指令可以同时修改元素的多个样式。
## Build a simple attribute directive
## 创建一个简单的属性型指令
An attribute directive minimally requires building a controller class annotated with
`@Directive`, which specifies the selector that identifies
the attribute.
The controller class implements the desired directive behavior.
属性型指令至少需要一个带有`@Directive`装饰器的控制器类。该装饰器指定了一个用于标识属性的选择器。
控制器类实现了指令需要的指令行为。
This page demonstrates building a simple _myHighlight_ attribute
directive to set an element's background color
when the user hovers over that element. You can apply it like this:
本章展示了如何创建一个简单的属性型指令 _myHighlight_ ,当用户把鼠标悬停在一个元素上时,改变它的背景色。你可以这样用它:
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (applied)" region="applied">
@ -65,11 +116,17 @@ when the user hovers over that element. You can apply it like this:
### Write the directive code
### 编写指令代码
Follow the [setup](guide/setup) instructions for creating a new local project
named <code>attribute-directives</code>.
按照[开发环境](guide/setup)的说明,创建一个名叫<code>attribute-directives</code>的项目文件夹。
Create the following source file in the indicated folder:
在指定的文件夹下创建下列源码文件:
<code-example path="attribute-directives/src/app/highlight.directive.1.ts" title="src/app/highlight.directive.ts">
@ -79,22 +136,40 @@ Create the following source file in the indicated folder:
The `import` statement specifies symbols from the Angular `core`:
`import`语句指定了从 Angular 的`core`库导入的一些符号。
1. `Directive` provides the functionality of the `@Directive` decorator.
`Directive`提供`@Directive`装饰器功能。
1. `ElementRef` [injects](guide/dependency-injection) into the directive's constructor
so the code can access the DOM element.
1. `Input` allows data to flow from the binding expression into the directive.
`ElementRef`[注入](guide/dependency-injection)到指令构造函数中。这样代码就可以访问 DOM 元素了。
1. `Input` allows data to flow from the binding expression into the directive.
`Input`将数据从绑定表达式传达到指令中。
Next, the `@Directive` decorator function contains the directive metadata in a configuration object
as an argument.
然后,`@Directive`装饰器函数以配置对象参数的形式,包含了指令的元数据。
`@Directive` requires a CSS selector to identify
the HTML in the template that is associated with the directive.
`@Directive`装饰器需要一个 CSS 选择器,以便从模板中识别出关联到这个指令的 HTML。
The [CSS selector for an attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors)
is the attribute name in square brackets.
Here, the directive's selector is `[myHighlight]`.
Angular locates all elements in the template that have an attribute named `myHighlight`.
[用于 attribute 的 CSS 选择器](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors)就是属性名称加方括号。
这里,指令的选择器是`[myHighlight]`Angular 将会在模板中找到所有带`myHighlight`属性的元素。
<div class="l-sub-section">
@ -102,15 +177,24 @@ Angular locates all elements in the template that have an attribute named `myHig
### Why not call it "highlight"?
### 为什么不直接叫做 "highlight"
Though *highlight* is a more concise name than *myHighlight* and would work,
a best practice is to prefix selector names to ensure
they don't conflict with standard HTML attributes.
This also reduces the risk of colliding with third-party directive names.
尽管*highlight* 是一个比 *myHighlight* 更简洁的名字,而且它确实也能工作。
但是最佳实践是在选择器名字前面添加前缀,以确保它们不会与标准 HTML 属性冲突。
它同时减少了与第三方指令名字发生冲突的危险。
Make sure you do **not** prefix the `highlight` directive name with **`ng`** because
that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose.
For a simple demo, the short prefix, `my`, helps distinguish your custom directive.
确认你**没有**给`highlight`指令添加**`ng`**前缀。
那个前缀属于 Angular使用它可能导致难以诊断的 bug。例如这个简短的前缀`my`可以帮助你区分自定义指令。
</div>
@ -120,23 +204,37 @@ After the `@Directive` metadata comes the directive's controller class,
called `HighlightDirective`, which contains the logic for the directive.
Exporting `HighlightDirective` makes it accessible to other components.
`@Directive`元数据之后就是该指令的控制器类,名叫`HighlightDirective`,它包含该指令的逻辑。
然后导出`HighlightDirective`,以便让它能从其它组件中访问到。
Angular creates a new instance of the directive's controller class for
each matching element, injecting an Angular `ElementRef`
into the constructor.
`ElementRef` is a service that grants direct access to the DOM element
through its `nativeElement` property.
Angular 会为每个匹配的元素创建一个指令控制器类的实例,并把 Angular 的`ElementRef`和`Renderer`注入进构造函数。
`ElementRef`是一个服务,它赋予我们通过它的`nativeElement`属性直接访问 DOM 元素的能力。
`Renderer`服务允许通过代码设置元素的样式。
## Apply the attribute directive
## 使用属性型指令
To use the new `HighlightDirective`, create a template that
applies the directive as an attribute to a paragraph (`<p>`) element.
In Angular terms, the `<p>` element is the attribute **host**.
要使用这个新的`HighlightDirective`,创建一个模板,把这个指令作为属性应用到一个段落(`p`)元素上。
用 Angular 的话说,`<p>`元素就是这个属性型指令的**宿主**。
Put the template in its own <code>app.component.html</code>
file that looks like this:
我们把这个模板放到它的<code>app.component.html</code>文件中,就像这样:
<code-example path="attribute-directives/src/app/app.component.1.html" title="src/app/app.component.html">
@ -146,6 +244,8 @@ file that looks like this:
Now reference this template in the `AppComponent`:
现在,在`AppComponent`中引用这个模板:
<code-example path="attribute-directives/src/app/app.component.ts" title="src/app/app.component.ts">
@ -157,6 +257,9 @@ Next, add an `import` statement to fetch the `Highlight` directive and
add that class to the `declarations` NgModule metadata. This way Angular
recognizes the directive when it encounters `myHighlight` in the template.
接下来,添加了一个`import`语句来获得`Highlight`指令类,并把这个类添加到 NgModule 元数据的`declarations`数组中。
这样,当 Angular 在模板中遇到`myHighlight`时,就能认出这是指令了。
<code-example path="attribute-directives/src/app/app.module.ts" title="src/app/app.module.ts">
@ -166,6 +269,8 @@ recognizes the directive when it encounters `myHighlight` in the template.
Now when the app runs, the `myHighlight` directive highlights the paragraph text.
运行应用,就会看到我们的指令确实高亮了段落中的文本。
<figure class='image-display'>
<img src="assets/images/devguide/attribute-directives/first-highlight.png" alt="First Highlight"></img>
@ -179,10 +284,17 @@ Now when the app runs, the `myHighlight` directive highlights the paragraph text
### Your directive isn't working?
Did you remember to add the directive to the `declarations` attribute of `@NgModule`?
### 你的指令没生效?
Did you remember to add the directive to the `declarations` attribute of `@NgModule`?
It is easy to forget!
你记着设置`@NgModule`的`declarations`数组了吗?它很容易被忘掉。
Open the console in the browser tools and look for an error like this:
打开浏览器调试工具的控制台,会看到像这样的错误信息:
<code-example format="nocode">
EXCEPTION: Template parse errors:
@ -197,6 +309,9 @@ in the module's `declarations` array.
After specifying `HighlightDirective` in the `declarations` array,
Angular knows it can apply the directive to components declared in this module.
Angular 检测到你正在尝试绑定到*某些东西*,但它不认识。所以它在`declarations`元数据数组中查找。
把`HighlightDirective`列在元数据的这个数组中Angular 就会检查对应的导入语句,从而找到`highlight.directive.ts`,并了解`myHightlight`的功能。
</div>
@ -207,18 +322,29 @@ It created an instance of the `HighlightDirective` class and
injected a reference to the `<p>` element into the directive's constructor
which sets the `<p>` element's background style to yellow.
总结Angular 在`<p>`元素上发现了一个`myHighlight`属性。
然后它创建了一个`HighlightDirective`类的实例,并把所在元素的引用注入到了指令的构造函数中。
在构造函数中,我们把`<p>`元素的背景设置为了黄色。
## Respond to user-initiated events
## 响应用户引发的事件
Currently, `myHighlight` simply sets an element color.
The directive could be more dynamic.
It could detect when the user mouses into or out of the element
and respond by setting or clearing the highlight color.
当前,`myHighlight`只是简单的设置元素的颜色。
这个指令应该在用户鼠标悬浮一个元素时,设置它的颜色。
Begin by adding `HostListener` to the list of imported symbols;
add the `Input` symbol as well because you'll need it soon.
先把`HostListener`加进导入列表中,同时再添加`Input`符号,因为我们很快就要用到它。
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports">
@ -229,6 +355,8 @@ add the `Input` symbol as well because you'll need it soon.
Then add two eventhandlers that respond when the mouse enters or leaves,
each adorned by the `HostListener` decorator.
然后使用`HostListener`装饰器添加两个事件处理器,它们会在鼠标进入或离开时进行响应。
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-methods)" region="mouse-methods">
@ -239,6 +367,8 @@ each adorned by the `HostListener` decorator.
The `@HostListener` decorator lets you subscribe to events of the DOM
element that hosts an attribute directive, the `<p>` in this case.
`@HostListener`装饰器引用属性型指令的宿主元素,在这个例子中就是`<p>`。
<div class="l-sub-section">
@ -247,10 +377,21 @@ element that hosts an attribute directive, the `<p>` in this case.
Of course you could reach into the DOM with standard JavaScript and and attach event listeners manually.
There are at least three problems with _that_ approach:
当然你可以通过标准的JavaScript方式手动给宿主 DOM 元素附加一个事件监听器。
但这种方法至少有三个问题:
1. You have to write the listeners correctly.
必须正确的书写事件监听器。
1. The code must *detach* the listener when the directive is destroyed to avoid memory leaks.
当指令被销毁的时候,必须*拆卸*事件监听器,否则会导致内存泄露。
1. Talking to DOM API directly isn't a best practice.
必须直接和 DOM API 打交道,应该避免这样做。
</div>
@ -260,6 +401,9 @@ The handlers delegate to a helper method that sets the color on the DOM element,
which you declare and initialize in the constructor.
这些处理器委托给了一个辅助方法它用于为DOM元素设置颜色`el`就是你在构造器中声明和初始化过的。
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (constructor)" region="ctor">
</code-example>
@ -268,6 +412,8 @@ which you declare and initialize in the constructor.
Here's the updated directive in full:
下面是修改后的指令代码:
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" title="src/app/highlight.directive.ts">
@ -275,8 +421,11 @@ Here's the updated directive in full:
Run the app and confirm that the background color appears when
the mouse hovers over the `p` and disappears as it moves out.
Run the app and confirm that the background color appears when the mouse hovers over the `p` and
disappears as it moves out.We run the app and confirm that the background color appears as we move the mouse over the `p` and
disappears as we move out.
运行本应用并确认:当把鼠标移到`p`上的时候,背景色就出现了,而移开的时候,它消失了。
<figure class='image-display'>
@ -288,11 +437,18 @@ the mouse hovers over the `p` and disappears as it moves out.
## Pass values into the directive with an _@Input_ data binding
## 使用数据绑定向指令传递值
Currently the highlight color is hard-coded _within_ the directive. That's inflexible.
In this section, you give the developer the power to set the highlight color while applying the directive.
现在的高亮颜色是硬编码在指令中的,这不够灵活。
我们应该让指令的使用者可以在模板中通过绑定来设置颜色。
Start by adding a `highlightColor` property to the directive class like this:
我们先把`highlightColor`属性添加到指令类中,就像这样:
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (highlightColor)" region="color">
@ -305,13 +461,22 @@ Start by adding a `highlightColor` property to the directive class like this:
### Binding to an _@Input_ property
### 绑定到_@Input_属性
Notice the `@Input` decorator. It adds metadata to the class that makes the directive's `highlightColor` property available for binding.
注意看`@Input`装饰器。它往类上添加了一些元数据,从而让该指令的`highlightColor`能用于绑定。
It's called an *input* property because data flows from the binding expression _into_ the directive.
Without that input metadata, Angular rejects the binding; see [below](guide/attribute-directives#why-input "Why add @Input?") for more about that.
它之所以称为*输入*属性,是因为数据流是从绑定表达式流向指令内部的。
如果没有这个元数据Angular就会拒绝绑定参见[稍后](guide/attribute-directives#why-input "为什么要添加@Input?")了解更多。
Try it by adding the following directive binding variations to the `AppComponent` template:
试试把下列指令绑定变量添加到`AppComponent`的模板中:
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (excerpt)" region="color-1">
@ -321,6 +486,8 @@ Try it by adding the following directive binding variations to the `AppComponent
Add a `color` property to the `AppComponent`.
把`color`属性添加到`AppComponent`中:
<code-example path="attribute-directives/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (class)" region="class">
@ -330,6 +497,8 @@ Add a `color` property to the `AppComponent`.
Let it control the highlight color with a property binding.
让它通过属性绑定来控制高亮颜色。
<code-example path="attribute-directives/src/app/app.component.1.html" linenums="false" title="src/app/app.component.html (excerpt)" region="color-2">
@ -339,6 +508,8 @@ Let it control the highlight color with a property binding.
That's good, but it would be nice to _simultaneously_ apply the directive and set the color _in the same attribute_ like this.
很不错,但还可以更好。我们可以在应用该指令时在同一个属性中设置颜色,就像这样:
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color">
@ -351,8 +522,14 @@ and sets the directive's highlight color with a property binding.
You're re-using the directive's attribute selector (`[myHighlight]`) to do both jobs.
That's a crisp, compact syntax.
`[myHighlight]`属性同时做了两件事:把这个高亮指令应用到了`<p>`元素上,并且通过属性绑定设置了该指令的高亮颜色。
我们复用了该指令的属性选择器`[myHighlight]`来同时完成它们。
这是清爽、简约的语法。
You'll have to rename the directive's `highlightColor` property to `myHighlight` because that's now the color property binding name.
我们还要把该指令的`highlightColor`改名为`myHighlight`,因为它是颜色属性目前的绑定名。
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (renamed to match directive selector)" region="color-2">
@ -362,15 +539,23 @@ You'll have to rename the directive's `highlightColor` property to `myHighlight`
This is disagreeable. The word, `myHighlight`, is a terrible property name and it doesn't convey the property's intent.
这可不好。因为`myHighlight`是一个糟糕的属性名,而且不能反映该属性的意图。
{@a input-alias}
### Bind to an _@Input_ alias
### 绑定到_@Input_别名
Fortunately you can name the directive property whatever you want _and_ **_alias it_** for binding purposes.
Restore the original property name and specify the selector as the alias in the argument to `@Input`.
幸运的是,我们可以随意命名该指令的属性,并且**给它指定一个用于绑定的别名**。
Restore the original property name and specify the selector as the alias in the argument to `@Input`.
恢复原始属性名,并在`@Input`的参数中把选择器`myHighlight`指定为别名。
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color property with alias)" region="color">
@ -382,8 +567,12 @@ Restore the original property name and specify the selector as the alias in the
_Inside_ the directive the property is known as `highlightColor`.
_Outside_ the directive, where you bind to it, it's known as `myHighlight`.
在指令内部,该属性叫`highlightColor`,在外部,当我们绑定到它时,它叫`myHighlight`。
You get the best of both worlds: the property name you want and the binding syntax you want:
这是最好的结果:理想的内部属性名,理想的绑定语法:
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color">
@ -391,9 +580,12 @@ You get the best of both worlds: the property name you want and the binding synt
Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it.
Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it.
If someone neglects to bind to `highlightColor`, highlight in red:
现在,我们绑定到了`highlightColor`属性,并修改`onMouseEnter()`方法来使用它。
如果有人忘了绑定到`highlightColor`,那就用红色进行高亮。
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (mouse enter)" region="mouse-enter">
@ -403,6 +595,8 @@ If someone neglects to bind to `highlightColor`, highlight in red:
Here's the latest version of the directive class.
这是最终版本的指令类。
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (excerpt)">
@ -412,12 +606,20 @@ Here's the latest version of the directive class.
## Write a harness to try it
## 写个测试程序试验下
It may be difficult to imagine how this directive actually works.
In this section, you'll turn `AppComponent` into a harness that
lets you pick the highlight color with a radio button and bind your color choice to the directive.
凭空想象该指令如何工作可不容易。
在本节,我们将把`AppComponent`改成一个测试程序,它让你可以通过单选按钮来选取高亮颜色,并且把你选取的颜色绑定到指令中。
Update <code>app.component.html</code> as follows:
把`app.component.html`修改成这样:
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (v2)" region="v2">
@ -427,6 +629,8 @@ Update <code>app.component.html</code> as follows:
Revise the `AppComponent.color` so that it has no initial value.
修改`AppComponent.color`,让它不再有初始值。
<code-example path="attribute-directives/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts (class)" region="class">
@ -436,6 +640,8 @@ Revise the `AppComponent.color` so that it has no initial value.
Here are the harness and directive in action.
下面是测试程序和指令的动图。
<figure class='image-display'>
<img src="assets/images/devguide/attribute-directives/highlight-directive-v2-anim.gif" alt="Highlight v.2"></img>
@ -446,16 +652,24 @@ Here are the harness and directive in action.
## Bind to a second property
## 绑定到第二个属性
This highlight directive has a single customizable property. In a real app, it may need more.
本例的指令只有一个可定制属性,真实的应用通常需要更多。
At the moment, the default color&mdash;the color that prevails until
the user picks a highlight color&mdash;is hard-coded as "red".
Let the template developer set the default color.
目前,默认颜色(它在用户选取了高亮颜色之前一直有效)被硬编码为红色。我们要让模板的开发者也可以设置默认颜色。
Add a second **input** property to `HighlightDirective` called `defaultColor`:
把第二个名叫`defaultColor`的**输入**属性添加到`HighlightDirective`中:
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (defaultColor)" region="defaultColor">
<code-example path="attribute-directives/src/app/highlight.directive.ts(defaultColor)" linenums="false" title="src/app/highlight.directive.ts(defaultColor) (excerpt)">
</code-example>
@ -464,6 +678,8 @@ Add a second **input** property to `HighlightDirective` called `defaultColor`:
Revise the directive's `onMouseEnter` so that it first tries to highlight with the `highlightColor`,
then with the `defaultColor`, and falls back to "red" if both properties are undefined.
修改该指令的`onMouseEnter`,让它首先尝试使用`highlightColor`进行高亮,然后用`defaultColor`,如果它们都没有指定,那就用红色作为后备。
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-enter)" region="mouse-enter">
@ -473,10 +689,15 @@ then with the `defaultColor`, and falls back to "red" if both properties are und
How do you bind to a second property when you're already binding to the `myHighlight` attribute name?
当已经绑定过`myHighlight`属性时,要如何绑定到第二个属性呢?
As with components, you can add as many directive property bindings as you need by stringing them along in the template.
The developer should be able to write the following template HTML to both bind to the `AppComponent.color`
and fall back to "violet" as the default color.
像组件一样,你也可以绑定到指令的很多属性,只要把它们依次写在模板中就行了。
开发者可以绑定到`AppComponent.color`,并且用紫罗兰色作为默认颜色,代码如下:
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (defaultColor)" region="defaultColor">
@ -487,8 +708,12 @@ and fall back to "violet" as the default color.
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
because you made it _public_ with the `@Input` decorator.
Angular之所以知道`defaultColor`绑定属于`HighlightDirective`,是因为我们已经通过`@Input`装饰器把它设置成了*公共*属性。
Here's how the harness should work when you're done coding.
当这些代码完成时,测试程序工作时的动图如下:
<figure class='image-display'>
<img src="assets/images/devguide/attribute-directives/highlight-directive-final-anim.gif" alt="Final Highlight"></img>
@ -499,15 +724,32 @@ Here's how the harness should work when you're done coding.
## Summary
## 总结
This page covered how to:
本章介绍了如何:
* [Build an **attribute directive**](guide/attribute-directives#write-directive) that modifies the behavior of an element.
[构建一个**属性型指令**](guide/attribute-directives#write-directive),它用于修改一个元素的行为。
* [Apply the directive](guide/attribute-directives#apply-directive) to an element in a template.
[把一个指令应用到](guide/attribute-directives#apply-directive)模板中的某个元素上。
* [Respond to **events**](guide/attribute-directives#respond-to-user) that change the directive's behavior.
[响应**事件**](guide/attribute-directives#respond-to-user)以改变指令的行为。
* [**Bind** values to the directive](guide/attribute-directives#bindings).
[把值**绑定**到指令中](guide/attribute-directives#bindings)。
The final source code follows:
最终的源码如下:
<code-tabs>
@ -541,13 +783,19 @@ The final source code follows:
You can also experience and download the <live-example title="Attribute Directive example"></live-example>.
你还可以体验和下载<live-example title="属性型指令范例"></live-example>.
### Appendix: Why add _@Input_?
### 附录:为什么要加*@Input*
In this demo, the `hightlightColor` property is an ***input*** property of
the `HighlightDirective`. You've seen it applied without an alias:
在这个例子中`hightlightColor`是`HighlightDirective`的一个***输入型***属性。我们见过它没有用别名时的代码:
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (color)" region="color">
@ -557,6 +805,8 @@ the `HighlightDirective`. You've seen it applied without an alias:
You've seen it with an alias:
也见过用别名时的代码:
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color)" region="color">
@ -568,32 +818,56 @@ Either way, the `@Input` decorator tells Angular that this property is
_public_ and available for binding by a parent component.
Without `@Input`, Angular refuses to bind to the property.
无论哪种方式,`@Input`装饰器都告诉Angular该属性是*公共的*,并且能被父组件绑定。
如果没有`@Input`Angular就会拒绝绑定到该属性。
You've bound template HTML to component properties before and never used `@Input`.
What's different?
但我们以前也曾经把模板HTML绑定到组件的属性而且从来没有用过`@Input`。
差异何在?
The difference is a matter of trust.
Angular treats a component's template as _belonging_ to the component.
The component and its template trust each other implicitly.
Therefore, the component's own template may bind to _any_ property of that component,
with or without the `@Input` decorator.
差异在于信任度不同。
Angular把组件的模板看做*从属于*该组件的。
组件和它的模板默认会相互信任。
这也就是意味着,组件自己的模板可以绑定到组件的*任意*属性,无论是否使用了`@Input`装饰器。
But a component or directive shouldn't blindly trust _other_ components and directives.
The properties of a component or directive are hidden from binding by default.
They are _private_ from an Angular binding perspective.
When adorned with the `@Input` decorator, the property becomes _public_ from an Angular binding perspective.
Only then can it be bound by some other component or directive.
但组件或指令不应该盲目的信任其它组件或指令。
因此组件或指令的属性默认是不能被绑定的。
从Angular绑定机制的角度来看它们是*私有*的,而当添加了`@Input`时,它们变成了*公共*的
只有这样,它们才能被其它组件或属性绑定。
You can tell if `@Input` is needed by the position of the property name in a binding.
你可以根据属性名在绑定中出现的位置来判定是否要加`@Input`。
* When it appears in the template expression to the ***right*** of the equals (=),
it belongs to the template's component and does not require the `@Input` decorator.
当它出现在等号***右侧***的模板表达式中时,它属于模板所在的组件,不需要`@Input`装饰器。
* When it appears in **square brackets** ([ ]) to the **left** of the equals (=),
the property belongs to some _other_ component or directive;
that property must be adorned with the `@Input` decorator.
当它出现在等号**左边**的**方括号([ ]**中时,该属性属于*其它*组件或指令,它必须带有`@Input` 装饰器。
Now apply that reasoning to the following example:
试用此原理分析下列范例:
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color">
@ -605,6 +879,12 @@ Now apply that reasoning to the following example:
The template and its component trust each other.
The `color` property doesn't require the `@Input` decorator.
`color`属性位于右侧的绑定表达式中,它属于模板所在的组件。
该模板和组件相互信任。因此`color`不需要`@Input`装饰器。
* The `myHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`,
not a property of the template's component. There are trust issues.
Therefore, the directive property must carry the `@Input` decorator.
Therefore, the directive property must carry the `@Input` decorator.
`myHighlight`属性位于左侧,它引用了`MyHighlightDirective`中一个*带别名的*属性,它不是模板所属组件的一部分,因此存在信任问题。
所以,该属性必须带`@Input`装饰器。

View File

@ -1,8 +1,8 @@
@title
Browser support
浏览器支持
@intro
Browser support and polyfills guide.
浏览器支持与填充 (Polyfill) 指南
@description
@ -10,6 +10,8 @@ Browser support and polyfills guide.
Angular supports most recent browsers. This includes the following specific versions:
Angular 支持大多数常用浏览器,包括下列版本:
<table>
@ -52,11 +54,27 @@ Angular supports most recent browsers. This includes the following specific vers
<tr>
<td>
latest
<p>
latest
</p>
<p>
最新版
</p>
</td>
<td>
latest
<p>
latest
</p>
<p>
最新版
</p>
</td>
<td>
@ -205,18 +223,28 @@ Angular's continuous integration process runs unit tests of the framework on all
using <a href="https://saucelabs.com/" target="_blank">SauceLabs</a> and
<a href="https://www.browserstack.com" target="_blank">Browserstack</a>.
Angular 在持续集成过程中,对每一个提交都会使用 <a href="https://saucelabs.com/" target="_blank">SauceLabs</a>
<a href="https://www.browserstack.com" target="_blank">Browserstack</a> 在上述所有浏览器上执行单元测试。
</div>
## Polyfills #
## 填充库 (polyfill) #
Angular is built on the latest standards of the web platform.
Targeting such a wide range of browsers is challenging because they do not support all features of modern browsers.
Angular 构建于 Web 平台的最新标准之上。
要支持这么多浏览器是一个不小的挑战,因为它们不支持现代浏览器的所有特性。
You can compensate by loading polyfill scripts ("polyfills") on the host web page (`index.html`)
that implement missing features in JavaScript.
你可以通过在宿主页面 (`index.html`) 中加载填充脚本 (“polyfills”) 来加以弥补,这些脚本实现了浏览器缺失的 JavaScript 特性。
<code-example path="quickstart/src/index.html" region="polyfills" title="quickstart/src/index.html" linenums="false">
</code-example>
@ -226,8 +254,12 @@ that implement missing features in JavaScript.
A particular browser may require at least one polyfill to run _any_ Angular application.
You may need additional polyfills for specific features.
要运行 Angular 应用,某些浏览器可能需要至少一个填充库。除此之外,如果要支持某些特定的特性,你可能还需要另一些填充库。
The tables below can help you determine which polyfills to load, depending on the browsers you target and the features you use.
下表将帮你决定加载哪些填充库,具体取决于目标浏览器和要用到的特性。
<div class="alert is-important">
@ -237,25 +269,48 @@ The suggested polyfills are the ones that run full Angular applications.
You may need additional polyfills to support features not covered by this list.
Note that polyfills cannot magically transform an old, slow browser into a modern, fast one.
这些建议的填充库是运行完整 Angular 应用所需的。
你可能还会需要另一些的填充库来支持没有出现在此列表中的哪些特性。
注意,这些填充库并没有神奇的魔力来把老旧、慢速的浏览器变成现代、快速的浏览器,它只是填充了 API。
</div>
### Mandatory polyfills ##
### 强制性填充库 ##
These are the polyfills required to run an Angular application on each supported browser:
下表是填充库对每个支持的浏览器都是需要的:
<table>
<tr style="vertical-align: top">
<th>
Browsers (desktop & mobile)
<p>
Browsers (desktop & mobile)
</p>
<p>
浏览器(桌面和移动)
</p>
</th>
<th>
Polyfills required
<p>
Polyfills required
</p>
<p>
需要的填充库
</p>
</th>
</tr>
@ -306,28 +361,59 @@ These are the polyfills required to run an Angular application on each supported
### Optional browser features to polyfill ##
### 可选浏览器特性的填充库 ##
Some features of Angular may require additional polyfills.
有些 Angular 特性可能需要额外的填充库。
For example, the animations library relies on the standard web animation API, which is only available in Chrome and Firefox today.
You'll need a polyfill to use animations in other browsers.
例如,动画库依赖于标准的 web 动画 API目前它只在 Chrome 和 Firefox 上可用。你可能需要一个填充库来在其它浏览器上使用动画功能。
Here are the features which may require additional polyfills:
下列特性可能需要更多填充库:
<table>
<tr style="vertical-align: top">
<th>
Feature
<p>
Feature
</p>
<p>
特性
</p>
</th>
<th>
Polyfill
<p>
Polyfill
</p>
<p>
填充库
</p>
</th>
<th style="width: 50%">
Browsers (desktop & mobile)
<p>
Browsers (desktop & mobile)
</p>
<p>
浏览器(桌面和移动)
</p>
</th>
</tr>
@ -335,17 +421,35 @@ Here are the features which may require additional polyfills:
<tr style="vertical-align: top">
<td>
<a href="./animations.html">Animations</a>
<p>
<a href="./animations.html">Animations</a>
</p>
<p>
<a href="./animations.html">动画</a>
</p>
</td>
<td>
[Web Animations](guide/browser-support#web-animations)
[Web 动画](guide/browser-support#web-animations)
</td>
<td>
All but Chrome and Firefox<br>Not supported in IE9
<p>
All but Chrome and Firefox<br>Not supported in IE9
</p>
<p>
除 Chrome 和 Firefox 外的所有,但不支持 IE9
</p>
</td>
</tr>
@ -353,7 +457,15 @@ Here are the features which may require additional polyfills:
<tr style="vertical-align: top">
<td>
<a href="../api/common/index/DatePipe-pipe.html" target="_blank">Date</a> <span>, </span> <a href="../api/common/index/CurrencyPipe-pipe.html" target="_blank">currency</a> <span>, </span> <a href="../api/common/index/DecimalPipe-pipe.html" target="_blank">decimal</a> <span> and </span> <a href="../api/common/index/PercentPipe-pipe.html" target="_blank">percent</a> <span> pipes</span>
<p>
<a href="../api/common/index/DatePipe-pipe.html" target="_blank">Date</a> <span>, </span> <a href="../api/common/index/CurrencyPipe-pipe.html" target="_blank">currency</a> <span>, </span> <a href="../api/common/index/DecimalPipe-pipe.html" target="_blank">decimal</a> <span> and </span> <a href="../api/common/index/PercentPipe-pipe.html" target="_blank">percent</a> <span> pipes</span>
</p>
<p>
<a href="../api/common/index/DatePipe-pipe.html" target="_blank">Date</a> <span></span> <a href="../api/common/index/CurrencyPipe-pipe.html" target="_blank">currency</a> <span></span> <a href="../api/common/index/DecimalPipe-pipe.html" target="_blank">decimal</a> <span>&nbsp;&nbsp;</span> <a href="../api/common/index/PercentPipe-pipe.html" target="_blank">percent</a> <span>&nbsp;管道</span>
</p>
</td>
<td>
@ -363,7 +475,15 @@ Here are the features which may require additional polyfills:
</td>
<td>
All but Chrome, Firefox, Edge, IE11 and Safari 10
<p>
All but Chrome, Firefox, Edge, IE11 and Safari 10
</p>
<p>
除了 Chrome、Firefox、Edge、IE11 和 Safari 10 外的所有浏览器
</p>
</td>
</tr>
@ -371,7 +491,15 @@ Here are the features which may require additional polyfills:
<tr style="vertical-align: top">
<td>
<a href="../api/common/index/NgClass-directive.html" target="_blank">NgClass</a> <span> on SVG elements</span>
<p>
<a href="../api/common/index/NgClass-directive.html" target="_blank">NgClass</a> <span>on SVG elements</span>
</p>
<p>
<span>在 SVG 元素上用&nbsp;</span> <a href="../api/common/index/NgClass-directive.html" target="_blank">NgClass</a>
</p>
</td>
<td>
@ -389,7 +517,15 @@ Here are the features which may require additional polyfills:
<tr style="vertical-align: top">
<td>
<a href="./server-communication.html">Http</a> <span> when sending and receiving binary data</span>
<p>
<a href="./server-communication.html">Http</a> <span> when sending and receiving binary data</span>
</p>
<p>
<span>&nbsp;</span> <a href="./server-communication.html">Http</a> <span>&nbsp;发送和接受二进制数据</span>
</p>
</td>
<td>
@ -409,23 +545,50 @@ Here are the features which may require additional polyfills:
### Suggested polyfills ##
### 建议的填充库 ##
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
下表中是用来测试框架本身的填充库,它们是应用程序的优质起点。
<table>
<tr>
<th>
Polyfill
<p>
Polyfill
</p>
<p>
填充库
</p>
</th>
<th>
License
<p>
Licence
</p>
<p>
授权方式
</p>
</th>
<th>
Size*
<p>
Size*
</p>
<p>
大小*
</p>
</th>
</tr>
@ -453,7 +616,15 @@ Below are the polyfills which are used to test the framework itself. They are a
</td>
<td>
Public domain
<p>
Public domain
</p>
<p>
公共域
</p>
</td>
<td>
@ -481,7 +652,7 @@ Below are the polyfills which are used to test the framework itself. They are a
<tr>
<td>
<a id='web-animations' href="https://github.com/web-animations/web-animations-js" target="_blank">Web Animations</a>
<a id='web-animations' href="https://github.com/web-animations/web-animations-js" target="_blank">Web Animations</a>
</td>
<td>
@ -547,4 +718,7 @@ Below are the polyfills which are used to test the framework itself. They are a
\* Figures are for minified and gzipped code,
computed with the <a href="http://closure-compiler.appspot.com/home" target="_blank">closure compiler</a>.
computed with the <a href="http://closure-compiler.appspot.com/home" target="_blank">closure compiler</a>.
\* 这些指标测量的是最小化 (minify) 并且 gzip 过的代码,使用 <a href="http://closure-compiler.appspot.com/home" target="_blank">closure compiler</a>
计算出的结果。

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,21 @@
@title
Cookbook
烹饪宝典
@intro
A collection of recipes for common Angular application scenarios.
一组常见 Angular 应用场景的“烹饪宝典”
@description
The *Cookbook* offers answers to common implementation questions.
这本*“烹饪宝典”*提供了在实施中一些常见问题的答案。
Each cookbook page is a collection of recipes focused on a particular Angular feature or application challenge
such as data binding, cross-component interaction, and communicating with a remote server via HTTP.
每个“烹饪宝典”章节都是一套“菜谱”每套菜谱都聚焦于一个特定的Angular特性或者来自应用方面的挑战比如数据绑定、跨组件通讯和通过HTTP与远程服务器交互等。
<div class="l-sub-section">
@ -19,20 +23,29 @@ such as data binding, cross-component interaction, and communicating with a remo
The cookbook is just getting started. Many more recipes are on the way.
这本烹饪宝典只是一个开始。我们正在编写更多“菜谱”。
</div>
Each cookbook links to a live sample with every recipe included.
Each cookbook links to a live sample with every recipe included.
Recipes are deliberately brief and code-centric.
Each recipe links to a relevant page of the [Developer Guide](guide) or the
[API Reference](api) where you can learn more about
the purpose, context, and design choices behind the code snippets.
“烹饪宝典”中的每个章节都链接了一个在线例子,包含“菜谱”的完整代码。
Recipes are deliberately brief and code-centric.
Each recipe links to a relevant page of the [Developer Guide](guide) or the
[API Reference](api)where you can learn more about the purpose, context, and design choices behind the code snippets.
我们有意保持着“菜谱”的简洁,并坚持以代码为中心。
每个“菜谱”都与[开发者指南](guide)或[API参考手册](api)里面的某个章节相关联。
在这些相关章节里,你可以学到更多关于代码片段背后的目的、上下文和设计选择等深层知识。
## Feedback
## 反馈
Post *documentation* requests and comments as
<a href="https://github.com/angular/angular.io/issues" target="_blank" title="Documentation issues on github">
<i>issues</i> on the angular.io</a> github repository.
@ -40,4 +53,11 @@ Fixes (small ones) are greatly appreciated as
<a href="https://github.com/angular/angular.io/pulls" target="_blank" title="Documentation PRs on github">
<i>pull requests</i></a>.
Post issues with *Angular itself* to the [angular](https://github.com/angular/angular) github repository.
请把对*文档*的建议和意见贴到<a href="https://github.com/angular/angular.io/issues" target="_blank" title="github上的文档问题">
angular.io的github <i>issues</i></a>
也非常欢迎把小的修改提交为<a href="https://github.com/angular/angular.io/pulls" target="_blank" title="Documentation PRs on github">
<i>pull requests</i></a>
Post issues with *Angular itself* to the [angular](https://github.com/angular/angular) github repository.
到[Angular仓库](https://github.com/angular/angular)提交*Angular本身*的问题。

View File

@ -1,8 +1,8 @@
@title
Change Log
更新记录
@intro
An annotated history of recent documentation improvements.
最新文档更新历史记录。
@description
@ -11,46 +11,102 @@ An annotated history of recent documentation improvements.
The Angular documentation is a living document with continuous improvements.
This log calls attention to recent significant changes.
我们将持续不断的更新和改进Angular文档。本日志记录了近期最重要的变更。
## Updated to Angular 4.0. Documentation for Angular 2.x can be found at [v2.angular.io](https://v2.angular.io).
## 更新到 Angular 4.0 。Angular 2.x 的文档在 [v2.angular.io](https://v2.angular.io) 。
## All mention of moduleId removed. "Component relative paths" cookbook deleted (2017-03-13)
## 移除了所有的moduleId引用。移除了“组件相对路径” 的烹饪书。(2017-03-13)
We added a new SystemJS plugin (systemjs-angular-loader.js) to our recommended SystemJS configuration.
This plugin dynamically converts "component-relative" paths in templateUrl and styleUrls to "absolute paths" for you.
我们往建议的SystemJS配置中新增了一个SystemJS插件 (systemjs-angular-loader.js) 。
这个插件可以帮你把templateUrl和styleUrls中的组件相对路径动态转换为绝对路径。
We strongly encourage you to only write component-relative paths.
That is the only form of URL discussed in these docs. You no longer need to write @Component({ moduleId: module.id }), nor should you.
我们强烈建议你只写组件相对路径。
这也是本文档中所使用的唯一形式。你不必再写`@Component({ moduleId: module.id })`,而且也不应该再这么写。
## NEW: Downloadable examples for each guide (2017-02-28)
## 新增:每篇指南都增加了可下载的范例程序 (2017-02-28)
Now you can download the sample code for any guide and run it locally.
Look for the new download links next to the "live example" links.
现在你可以为任何一篇指南下载范例程序,并且在本地运行它了。
请在“在线例子”的链接后面查找新的下载链接。
## Template Syntax/Structural Directives: refreshed (2017-02-06)
## 模板语法/结构型指令:更新了 (2017-02-06)
The [_Template-Syntax_](guide/template-syntax) and [_Structural Directives_](guide/structural-directives)
guides were significantly revised for clarity, accuracy, and current recommended practices.
Discusses `<ng-container>`.
Revised samples are more clear and cover all topics discussed.
对[模板语法](guide/template-syntax) 和 [结构型指令](guide/structural-directives)这两篇指南做了大幅修改,来让它们更加清晰、准确,并符合当前的最佳实践。
讨论了`<ng-container>`。
修改了例子,来让它们更清晰,并且覆盖了所有讨论到的主题。
## NEW: Samples re-structured with `src/` folder (2017-02-02)
## 新增:调整了范例程序的结构,移到了`src/`文件夹 (2017-02-02)
All documentation samples have been realigned with the default folder structure of the Angular CLI.
That's a step along the road to basing the sample in the Angular CLI.
But it's also good in its own right.
It helps clearly separate app code from setup and configuration files.
所有的文档范例都已经向Angular CLI的默认文件夹结构看齐了。
这是把范例迁移到Angular CLI过程中的一步。
不过也不仅是为了迁移,它确实能帮我们把应用代码从环境代码和配置代码中分离出来。
All samples now have a `src/` folder at the project root.
The former `app/` folder moves under `src/`.
Read about moving your existing project to this structure in
<a href="https://github.com/angular/quickstart#updating-to-a-newer-version-of-the-quickstart-repo" target="_blank" target="Migrating samples/quickstart app to the src folder">
the QuickStart repo update instructions</a>.
我们已经把所有范例改成了使用项目根目录下的`src/`文件夹。
也就是把以前的`app/`文件夹移到了`src/`文件夹下面。
要了解如何对你的现有工程进行这种迁移,请参阅<a href="https://github.com/angular/quickstart#updating-to-a-newer-version-of-the-quickstart-repo" target="_blank" target="把范例中的应用迁移到src文件夹">QuickStart中的迁移指南</a>
Notably:
要点:
* `app/main.ts` moved to `src/main.ts`.
把`app/main.ts`移到`src/main.ts`。
* `app/` moved to `src/app/`.
把`app/`移到`src/app/`。
* `index.html`, `styles.css` and `tsconfig.json` moved inside `src/`.
把`index.html`、`styles.css`和`tsconfig.json`移到`src/`中。
* `systemjs.config.js` now imports `main.js` instead of `app`.
`systemjs.config.js`现在要导入`main.js`而不是`app`。
* Added `lite-server` configuration (`bs-config.json`) to serve `src/`.
新增了一个`lite-server`配置(`bs-config.json`)以便在`src/`下启动开发服务器。
## NEW: Reactive Forms guide (2017-01-31)
## 新增响应式Reactive表单指南 (2017-01-31)
The new [**Reactive Forms**](guide/reactive-forms) guide explains how and why to build a "reactive form".
"Reactive Forms" are the code-based counterpart to the declarative "Template Driven" forms approach
introduced in the [Forms](guide/forms) guide.
@ -58,168 +114,330 @@ Check it out before you decide how to add forms to your app.
Remember also that you can use both techniques in the same app,
choosing the approach that best fits each scenario.
新的[**响应式表单**](guide/reactive-forms)指南解释了如何以及何时构建“响应式表单”。
“响应式表单”是基于代码的表单构建方式,与[表单](guide/forms)中介绍的声明“模板驱动”表单的方法相对。
在你决定如何往应用中添加表单之前,建议先读读那一章。
同时,别忘了你可以在同一个应用中同时使用这两种技术,根据场景来选择最合适的方法。
## NEW: Deployment guide (2017-01-30)
## 新增:部署指南 (2017-01-30)
The new [Deployment](guide/deployment) guide describes techniques for putting your application on a server.
It includes important advice on optimizing for production.
新的[部署指南](guide/deployment)讲的是如何把应用放到服务器上。
其中包括了为生产环境进行优化的重要建议。
## Hierarchical Dependency Injection: refreshed (2017-01-13)
## 多级依赖注入:更新完毕 (2017-01-13)
[Hierarchical Dependency Injection](guide/hierarchical-dependency-injection) guide is significantly revised.
Closes issue #3086.
Revised samples are clearer and cover all topics discussed.
Revised samples are clearer and cover all topics discussed
[多级依赖注入](guide/hierarchical-dependency-injection)做了显著修改。关闭了issue #3086。修改过的范例更加清晰,而且覆盖了讨论到的全部主题。
## Miscellaneous (2017-01-05)
## 杂项 (2017-01-05)
* [Setup](guide/setup) guide:
added (optional) instructions on how to remove _non-essential_ files.
added (optional) instructions on how to remove _non-essential_ files.
[环境搭建](guide/setup)指南:
添加了(可选的)步骤说明,告诉你如何移除*非核心*文件。
* No longer consolidate RxJS operator imports in `rxjs-extensions` file; each file should import what it needs.
不再在`rxjs-extensions`文件中统一导入RxJS的操作符每个文件应该各自导入它自己所需的。
* All samples prepend template/style URLs with `./` as a best practice.
所有范例都在模板/样式的URL之前添加`./`前缀 …… 而且你在实际开发中也应该这么做。
* [Style Guide](guide/style-guide): copy edits and revised rules.
[风格指南](guide/style-guide):复制了编辑过的和修改过的规则。
## Router: more detail (2016-12-21)
## 路由:更详细 (2016-12-21)
Added more information to the [Router](guide/router) guide
including sections named outlets, wildcard routes, and preload strategies.
往[路由指南](guide/router)中添加了更多信息包括“命名出口Outlet”、通配符路由和预加载策略。
## HTTP: how to set default request headers (and other request options) (2016-12-14)
## Http如何设置默认的请求头以及其它配置项 (2016-12-14)
Added section on how to set default request headers (and other request options) to
[HTTP](guide/server-communication#override-default-request-options) guide.
往[Http指南](guide/server-communication#override-default-request-options)中添加了一节“如何设置默认的请求头(以及其它配置项)”
## Testing: added component test plunkers (2016-12-02)
## 测试添加了组件测试的plunker范例 (2016-12-02)
Added two plunkers that each test _one simple component_ so you can write a component test plunker of your own: <live-example name="setup" plnkr="quickstart-specs">one</live-example> for the QuickStart seed's `AppComponent` and <live-example name="testing" plnkr="banner-specs">another</live-example> for the Testing guide's `BannerComponent`.
Linked to these plunkers in [Testing](guide/testing#live-examples) and [Setup anatomy](guide/setup-systemjs-anatomy) guides.
添加了两个plunker例子每个都测试一个简单的组件以便你可以自己在plunker中写组件测试<live-example name="setup" plnkr="quickstart-specs">一个</live-example>用于测试快速起步中的`AppComponent`<live-example name="testing" plnkr="banner-specs">另一个</live-example>用于测试“测试”章节的`BannerComponent`。
并在[测试](guide/testing#live-examples)和[环境设置剖析](guide/setup-systemjs-anatomy)中链接到它们。
## Internationalization: pluralization and _select_ (2016-11-30)
The [Internationalization (i18n)](cookbook/i18n) guide explains how to handle pluralization and
## 国际化:单复数和`select` (2016-11-30)
The [Internationalization (i18n)](cookbook/i18n) guide explains how to handle pluralization and
translation of alternative texts with `select`.
The sample demonstrates these features too.
[国际化 (i18n)](guide/i18n)解释了如何处理单复数问题,和如何使用`select`来翻译候选内容。
例子中也演示了这些特性。
## Testing: karma file updates (2016-11-30)
## 测试更新了karma文件 (2016-11-30)
* `karma.config` + `karma-test-shim` can handle multiple spec source paths;
see quickstart issue: [angular/quickstart#294](https://github.com/angular/quickstart/issues/294).
`karma.config` + `karma-test-shim`可以处理多个测试源文件路径了,参见[angular/quickstart#294](https://github.com/angular/quickstart/issues/294)。
* Displays Jasmine Runner output in the karma-launched browser.
在启动了karma的浏览器中显示Jasmine的输出。
## QuickStart Rewrite (2016-11-18)
## 全新《快速起步》 (2016-11-18)
The QuickStart is completely rewritten so that it actually is quick.
It references a minimal "Hello Angular" app running in Plunker.
The new [Setup](guide/setup) page tells you how to install a local development environment
by downloading (or cloning) the QuickStart github repository.
You are no longer asked to copy-and-paste code into setup files that were not explained anyway.
完全重写了《快速起步》,变得更加快速。
它使用了在 Plunker 中运行的最小化的 “Hello Angular” 应用。
新添加的[搭建本地开发环境](guide/setup)页面解释了如何通过下载或者克隆 QuickStart github 库来安装本地开发环境。
你将不再需要拷贝粘贴代码到一些并没有对其解释的配置文件中。
## Sync with Angular v.2.2.0 (2016-11-14)
Docs and code samples updated and tested with Angular v.2.2.0.
## 与Angular v.2.2.0同步(2016-11-14)
Docs and code samples updated and tested with Angular v.2.2.0 .
使用Angular v.2.2.0更新和测试所有文档和代码例子。
## UPDATE: NgUpgrade Guide for the AOT friendly _upgrade/static_ module (2016-11-14)
## 更新用于AoT的_upgrade/static_模块NgUpgrade指南 (2016-11-14)
The updated [NgUpgrade Guide](guide/upgrade) guide covers the
new AOT friendly `upgrade/static` module
released in v.2.2.0, which is the recommended
facility for migrating from AngularJS to Angular.
The documentation for the version prior to v.2.2.0 has been removed.
更新的[NgUpgrade指南](guide/upgrade)覆盖在v.2.2.0发布的AoT`upgrade/static`模块,
是从AngularJS升级至Angular的推荐工具。
删除早于v.2.2.0版本的文档。
## ES6 described in "TypeScript to JavaScript" (2016-11-14)
## 在“从TypeScript到JavaScript”增加ES6的描述 (2016-11-14)
The updated [TypeScript to JavaScript](cookbook/ts-to-js) cookbook
now explains how to write apps in ES6/7
更新了“[从TypeScript到JavaScript](cookbook/ts-to-js)”烹饪宝典解释如何使用ES6/7编写应用
by translating the common idioms in the TypeScript documentation examples
(and elsewhere on the web) to ES6/7 and ES5.
将TypeScript文档示例中以及网站其它地方的习惯用法翻译成ES6/7和ES5。
## Sync with Angular v.2.1.1 (2016-10-21)
Docs and code samples updated and tested with Angular v.2.1.1.
## 与Angular v.2.1.1 同步(2016-10-21)
Docs and code samples updated and tested with Angular v.2.1.1.
使用Angular v.2.1.1更新和测试所有文档和代码例子。
## npm _@types_ packages replace _typings_ (2016-10-20)
## 使用npm的_@types_包替换_typings_ (2016-10-20)
Documentation samples now get TypeScript type information for 3rd party libraries
from npm `@types` packages rather than with the _typings_ tooling.
The `typings.json` file is gone.
文档例子现在从npm的`@types`第三方库获取TypeScript类型信息不再使用_typings_。
删除`typings.json`文件。
The [AngularJS Upgrade](guide/upgrade) guide reflects this change.
The `package.json` installs `@types/angular` and several `@types/angular-...`
packages in support of upgrade; these are not needed for pure Angular development.
"[从AngularJS升级](guide/upgrade)"指南反映了这个变化。
`package.json`安装`@types/angular`和一些`@types/angular-...`包来支持升级。它们在纯Angular开发中是不需要的。
## "Template Syntax" explains two-way data binding syntax (2016-10-20)
## "模板语法"添加了双向数据绑定语法的解释(2016-10-20)
Demonstrates how to two-way data bind to a custom Angular component and
re-explains `[(ngModel)]` in terms of the basic `[()]` syntax.
展示了如何在自定义Angular组件中双向数据绑定用基础`[()]`重新解释`[(ngModel)]`。
## BREAKING CHANGE: `in-memory-web-api` (v.0.1.11) delivered as esm umd (2016-10-19)
## 破坏性变化:`in-memory-web-api` (v.0.1.11) 以esm umd的形式发布 (2016-10-19)
This change supports ES6 developers and aligns better with typical Angular libraries.
It does not affect the module's API but it does affect how you load and import it.
See the <a href="https://github.com/angular/in-memory-web-api/blob/master/CHANGELOG.md#0113-2016-10-20" target="_blank">change note</a>
in the `in-memory-web-api` repo.
这个变化支持ES6开发者并与典型的Angular库看齐。
它不会影响模块的API但是它改变了加载和导入它的方式。
参见`in-memory-web-api`库的<a href="https://github.com/angular/in-memory-web-api/blob/master/CHANGELOG.md#0113-2016-10-20" target="_blank">变更记录</a>
## "Router" _preload_ syntax and _:enter_/_:leave_ animations (2016-10-19)
## "路由器"_预加载_语法和_:enter_/_:leave_动画(2016-10-19)
The router can lazily _preload_ modules _after_ the app starts and
_before_ the user navigates to them for improved perceived performance.
New `:enter` and `:leave` aliases make animation more natural.
路由器可以在应用启动_之后_和用户导航到惰性加载模块_之前_预先加载惰性模块以增强性能。
New `:enter` and `:leave` aliases make animation more natural.
新`:enter`和`:leave`语法,让动画更加自然。
## Sync with Angular v.2.1.0 (2016-10-12)
Docs and code samples updated and tested with Angular v.2.1.0.
## 与Angular v.2.1.0同步(2016-10-12)
Docs and code samples updated and tested with Angular v.2.1.0 .
使用Angular v.2.1.0更新和测试所有文档和代码例子。
## NEW "Ahead of time (AOT) Compilation" cookbook (2016-10-11)
## 添加了新的“预编译(AoT)"烹饪书(2016-10-11)
The NEW [Ahead of time (AOT) Compilation](cookbook/aot-compiler) cookbook
explains what AOT compilation is and why you'd want it.
It demonstrates the basics with a QuickStart app
followed by the more advanced considerations of compiling and bundling the Tour of Heroes.
全新[预编译(AoT)](cookbook/aot-compiler)烹饪书介绍了什么是AoT编译和为何你需要它。
它以**快速起步**应用程序开始讲解,接着介绍了编译和构建**英雄指南**的更高级的注意事项。
## Sync with Angular v.2.0.2 (2016-10-6)
Docs and code samples updated and tested with Angular v.2.0.2.
## 与Angular v.2.0.2同步 (2016-10-6)
Docs and code samples updated and tested with Angular v.2.0.2 .
使用Angular v.2.0.2更新和测试所有文档和代码例子。
## "Routing and Navigation" guide with the _Router Module_ (2016-10-5)
## 在“路由和导航”向导中添加**路由模块** (2016-10-5)
The [Routing and Navigation](guide/router) guide now locates route configuration
in a _Routing Module_.
The _Routing Module_ replaces the previous _routing object_ involving the `ModuleWithProviders`.
[Routing and Navigation](guide/router)现在在**路由模块**中设置路由配置。
**路由模块**替换之前的**路由对象**,使用了`ModuleWithProviders`。
[Routing and Navigation](guide/router)
[路由与导航](guide/router)
All guided samples with routing use the _Routing Module_ and prose content has been updated,
most conspicuously in the
[NgModule](guide/ngmodule) guide and [NgModule FAQ](cookbook/ngmodule-faq) cookbook.
所有使用路由的例子都使用**路由模块**,相关内容也被更新。更新最多的是[Angular模块NgModule](guide/ngmodule)章和[Angular模块常见问题](cookbook/ngmodule-faq)烹饪书。
## New "Internationalization" Cookbook (2016-09-30)
## 全新“国际化”烹饪书(2016-09-30)
Added a new [Internationalization (i18n)](cookbook/i18n) cookbook that shows how
to use Angular "i18n" facilities to translate template text into multiple languages.
添加了新的[国际化(i18n)](cookbook/i18n)烹饪书展示了如何使用Angular的“i18n”工具来讲模板文本翻译到多种语言。
## "angular-in-memory-web-api" package rename (2016-09-27)
## 重命名“angular-in-memory-web-api”包(2016-09-27)
Many samples use the `angular-in-memory-web-api` to simulate a remote server.
This library is also useful to you during early development before you have a server to talk to.
许多例子使用了`angular-in-memory-web-api`来模拟远程服务器。
这个库在你拥有服务器之前的早期开发阶段也很有用。
The package name was changed from "angular2-in-memory-web-api" which is still frozen-in-time on npm.
The new "angular-in-memory-web-api" has new features.
<a href="https://github.com/angular/in-memory-web-api/blob/master/README.md" target="_blank">Read about them on github</a>.
这个包的名字从“angular2-in-memory-web-api”仍然有效但不再更新了重新命名了。
新的“angular-in-memory-web-api”有新的功能。
<a href="https://github.com/angular/in-memory-web-api/blob/master/README.md" target="_blank">到github获得更多详情</a>.
## "Style Guide" with _NgModules_ (2016-09-27)
## “风格指南”中添加了_NgModules_(2016-09-27)
[StyleGuide](guide/style-guide) explains recommended conventions for Angular modules (NgModule).
[StyleGuide](guide/style-guide)解释了我们为Angular模块NgModule而推荐的约定。
Barrels now are far less useful and have been removed from the style guide;
they remain valuable but are not a matter of Angular style.
Also relaxed the rule that discouraged use of the `@Component.host` property.
现在,封装桶不再那么重要,风格指南已经移除了它们。
它们仍然很有价值但是它们与Angular风格无关。
我们同时对**不推荐使用`@Component.host`属性**的规则有所放宽。
## _moduleId: module.id_ everywhere (2016-09-25)
## moduleId到处添加module.id(2016-09-25)
Sample components that get their templates or styles with `templateUrl` or `styleUrls`
have been converted to _module-relative_ URLs.
Added the `moduleId: module.id` property-and-value to their `@Component` metadata.
在所有使用`templateUrl`或者`styleUrls`来获取模板或样式的例子组件都被转换为**相对模块**的URL。
我们添加了`moduleId: module.id`到它们的`@Component`元数据。
This change is a requirement for compilation with AOT compiler when the app loads
modules with SystemJS as the samples currently do.
当应用像例子当前使用的方法一样 - 使用SystemJS加载模块时本更新是AoT编译器的前提条件。
## "Lifecycle Hooks" guide simplified (2016-09-24)
The [Lifecycle Hooks](guide/lifecycle-hooks) guide is shorter, simpler, and
draws more attention to the order in which Angular calls the hooks.
## 简化了“生命周期钩子”章(2016-09-24)
The [Lifecycle Hooks](guide/lifecycle-hooks) guide is shorter, simpler, and
draws more attention to the order in which Angular calls the hooks.
[生命周期钩子](guide/lifecycle-hooks)章现在更加简短并且对强调了Angular是以什么顺序来调用钩子方法的。

View File

@ -1,5 +1,5 @@
@title
Cheat Sheet
速查表
@description
// SCSS from
@ -102,7 +102,7 @@ is available to <code>declarations</code> of this module.</p>
</tr><tr>
<td><code>&lt;p&nbsp;<b>*myUnless</b>="myExpression"&gt;...&lt;/p&gt;</code></td>
<td><p>The <code>*</code> symbol turns the current element into an embedded template. Equivalent to:
<code>&lt;ng-template [myUnless]="myExpression"&gt;&lt;p&gt;...&lt;/p&gt;&lt;/ng-template&gt;</code></p>
<code>&lt;template [myUnless]="myExpression"&gt;&lt;p&gt;...&lt;/p&gt;&lt;/template&gt;</code></p>
</td>
</tr><tr>
<td><code>&lt;p&gt;Card&nbsp;No.:&nbsp;<b>{{cardNumber&nbsp;|&nbsp;myCardNumberFormatter}}</b>&lt;/p&gt;</code></td>
@ -139,7 +139,7 @@ is available to <code>declarations</code> of this module.</p>
<td><p>Turns the li element and its contents into a template, and uses that to instantiate a view for each item in list.</p>
</td>
</tr><tr>
<td><code>&lt;div&nbsp;<b>[ngSwitch]</b>="conditionExpression"&gt;<br>&nbsp;&nbsp;&lt;ng-template&nbsp;<b>[<b>ngSwitchCase</b>]</b>="case1Exp"&gt;...&lt;/ng-template&gt;<br>&nbsp;&nbsp;&lt;ng-template&nbsp;<b>ngSwitchCase</b>="case2LiteralString"&gt;...&lt;/ng-template&gt;<br>&nbsp;&nbsp;&lt;ng-template&nbsp;<b>ngSwitchDefault</b>&gt;...&lt;/ng-template&gt;<br>&lt;/div&gt;</code></td>
<td><code>&lt;div&nbsp;<b>[ngSwitch]</b>="conditionExpression"&gt;<br>&nbsp;&nbsp;&lt;template&nbsp;<b>[<b>ngSwitchCase</b>]</b>="case1Exp"&gt;...&lt;/template&gt;<br>&nbsp;&nbsp;&lt;template&nbsp;<b>ngSwitchCase</b>="case2LiteralString"&gt;...&lt;/template&gt;<br>&nbsp;&nbsp;&lt;template&nbsp;<b>ngSwitchDefault</b>&gt;...&lt;/template&gt;<br>&lt;/div&gt;</code></td>
<td><p>Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of <code>conditionExpression</code>.</p>
</td>
</tr><tr>

View File

@ -4,27 +4,50 @@
Good tools make application development quicker and easier to maintain than
if you did everything by hand.
好的工具能让开发更加简单快捷。
The [**Angular CLI**](https://cli.angular.io/) is a **_command line interface_** tool
that can create a project, add files, and perform a variety of ongoing development tasks such
that can create a project, add files, and perform a variety of ongoing development tasks such
as testing, bundling, and deployment.
The goal in this guide is to build and run a simple Angular
application in TypeScript, using the Angular CLI
[**Angular CLI**](https://cli.angular.io/)是一个**命令行界面**工具,它可以创建项目、添加文件以及执行一大堆开发任务,比如测试、打包和发布。
The goal in this guide is to build and run a simple Angular
application in TypeScript, using the Angular CLI
while adhering to the [Style Guide](guide/guide/style-guide) recommendations that
benefit _every_ Angular project.
在这一章CLI快速起步中我们的目标是构建并运行一个超级简单的Angular应用。我们会使用Angular-CLI来让每个Angular应用从[风格指南](guide/guide/style-guide)中获益。
By the end of the chapter, you'll have a basic understanding of development with the CLI
and a foundation for both these documentation samples and for real world applications.
在本章的末尾我们会通过CLI对开发过程有一个最基本的理解并将其作为其它文档范例以及真实应用的基础。
You'll pursue these ends in the following high-level steps:
我们通过下列三大步来达到目的:
1. [Set up](guide/cli-quickstart#devenv) the development environment.
[设置](guide/cli-quickstart#devenv)开发环境。
2. [Create](guide/cli-quickstart#create-proj) a new project and skeleton application.
[创建](guide/cli-quickstart#create-proj)新项目以及应用的骨架。
3. [Serve](guide/cli-quickstart#serve) the application.
启动[开发服务器](guide/cli-quickstart#serve)。
4. [Edit](guide/cli-quickstart#first-component) the application.
[编辑](guide/cli-quickstart#first-component)该应用。
And you can also <a href="/resources/zips/cli-quickstart/cli-quickstart.zip">download the example.</a>
你还可以<a href="/resources/zips/cli-quickstart/cli-quickstart.zip">下载这个例子</a>
<h2 id='devenv'>
@ -33,11 +56,22 @@ And you can also <a href="/resources/zips/cli-quickstart/cli-quickstart.zip">dow
<h2 id='devenv'>
步骤1. 设置开发环境
</h2>
You need to set up your development environment before you can do anything.
在开始工作之前,我们必须设置好开发环境。
Install **[Node.js® and npm](https://nodejs.org/en/download/)**
if they are not already on your machine.
如果你的机器上还没有**[Node.js®和npm](https://nodejs.org/en/download/)**,请先安装它们。
<div class="l-sub-section">
@ -46,12 +80,19 @@ if they are not already on your machine.
by running `node -v` and `npm -v` in a terminal/console window.
Older versions produce errors, but newer versions are fine.
请先在终端/控制台窗口中运行命令 `node -v``npm -v`
**来验证一下你正在运行 node `6.9.x` 和 npm `3.x.x` 以上的版本。**
更老的版本可能会出现错误,更新的版本则没问题。
</div>
Then **install the [Angular CLI](https://github.com/angular/angular-cli)** globally.
然后全局安装 **[Angular CLI](https://github.com/angular/angular-cli)** 。
<code-example language="sh" class="code-shell">
npm install -g @angular/cli
@ -61,17 +102,27 @@ Then **install the [Angular CLI](https://github.com/angular/angular-cli)** globa
<h2 id='create-project'>
<h2 id='create-proj'>
Step 2. Create a new project
</h2>
<h2 id='create-proj'>
步骤2. 创建新项目
</h2>
Open a terminal window.
打开终端窗口。
Generate a new project and skeleton application by running the following commands:
运行下列命令来生成一个新项目以及应用的骨架代码:
<code-example language="sh" class="code-shell">
ng new my-app
@ -87,6 +138,9 @@ Generate a new project and skeleton application by running the following command
Patience please.
It takes time to set up a new project, most of it spent installing npm packages.
请耐心等待。
创建新项目需要花费很多时间大多数时候都是在安装那些npm包。
</div>
@ -94,13 +148,23 @@ It takes time to set up a new project, most of it spent installing npm packages.
<h2 id='serve'>
Step 3: Serve the application
<p>
Step 3: Serve the application
</p>
<p>
步骤3. 启动开发服务器
</p>
</h2>
Go to the project directory and launch the server.
进入项目目录,并启动服务器。
<code-example language="sh" class="code-shell">
cd my-app
@ -113,11 +177,17 @@ Go to the project directory and launch the server.
The `ng serve` command launches the server, watches your files,
and rebuilds the app as you make changes to those files.
`ng serve`命令会启动开发服务器,监听文件变化,并在修改这些文件时重新构建此应用。
Using the `--open` (or just `-o`) option will automatically open your browser
on `http://localhost:4200/`.
使用`--open`(或`-o`)参数可以自动打开浏览器并访问`http://localhost:4200/`。
Your app greets you with a message:
本应用会用一条消息来跟你打招呼:
<figure class='image-display'>
<img src='assets/images/devguide/cli-quickstart/app-works.png' alt="The app works!"></img>
@ -132,13 +202,25 @@ Your app greets you with a message:
<h2 id='first-component'>
步骤4. 编辑我们的第一个Angular组件
</h2>
The CLI created the first Angular component for you.
This is the _root component_ and it is named `app-root`.
You can find it in `./src/app/app.component.ts`.
这个CLI为我们创建了第一个Angular组件。
它就是名叫`app-root`的*根组件*。
你可以在`./src/app/app.component.ts`目录下找到它。
Open the component file and change the `title` property from _app works!_ to _My First Angular App_:
打开这个组件文件,并且把`title`属性从 _app works!_ 改为 _My First Angular App_
<code-example path="cli-quickstart/src/app/app.component.ts" region="title" title="src/app/app.component.ts" linenums="false">
@ -148,8 +230,12 @@ Open the component file and change the `title` property from _app works!_ to _My
The browser reloads automatically with the revised title. That's nice, but it could look better.
浏览器会自动刷新,而我们会看到修改之后的标题。不错,不过它还可以更好看一点。
Open `src/app/app.component.css` and give the component some style.
打开 `src/app/app.component.css` 并给这个组件设置一些样式
<code-example path="cli-quickstart/src/app/app.component.css" title="src/app/app.component.css" linenums="false">
@ -165,37 +251,66 @@ Open `src/app/app.component.css` and give the component some style.
Looking good!
漂亮!
## What's next?
## 接下来呢?
That's about all you'd expect to do in a "Hello, World" app.
如你所愿我们完成了这个“Hello, World”应用。
You're ready to take the [Tour of Heroes Tutorial](guide/tutorial) and build
a small application that demonstrates the great things you can build with Angular.
现在,你可以开始[英雄指南](guide/tutorial)教程通过构建一个小型应用来学习如何用Angular构建各种大型应用了。
Or you can stick around a bit longer to learn about the files in your brand new project.
或者,你也可以稍等一会儿,学学在这个新项目中的文件都是干什么用的。
## Project file review
## 项目文件概览
An Angular CLI project is the foundation for both quick experiments and enterprise solutions.
Angular CLI项目是做快速试验和开发企业解决方案的基础。
The first file you should check out is `README.md`.
It has some basic information on how to use CLI commands.
Whenever you want to know more about how Angular CLI works make sure to visit
[the Angular CLI repository](https://github.com/angular/angular-cli) and
[Wiki](https://github.com/angular/angular-cli/wiki).
Some of the generated files might be unfamiliar to you.
你首先要看的文件是`README.md`。
它提供了一些如何使用CLI命令的基础信息。
如果你想了解 Angular CLI 的工作原理,请访问 [Angular CLI 的仓库](https://github.com/angular/angular-cli)及其
[Wiki](https://github.com/angular/angular-cli/wiki)。
Some of the generated files might be unfamiliar to you.
有些生成的文件你可能觉得陌生。接下来我们就讲讲它们。
### The `src` folder
### `src`文件夹
Your app lives in the `src` folder.
All Angular components, templates, styles, images, and anything else your app needs go here.
Any files outside of this folder are meant to support building your app.
你的应用代码位于`src`文件夹中。
所有的Angular组件、模板、样式、图片以及你的应用所需的任何东西都在那里。
这个文件夹之外的文件都是为构建应用提供支持用的。
<div class='filetree'>
@ -318,11 +433,27 @@ Any files outside of this folder are meant to support building your app.
<tr>
<th>
File
<p>
File
</p>
<p>
文件
</p>
</th>
<th>
Purpose
<p>
Purpose
</p>
<p>
用途
</p>
</th>
</tr>
@ -338,7 +469,10 @@ Any files outside of this folder are meant to support building your app.
Defines the `AppComponent` along with an HTML template, CSS stylesheet, and a unit test.
It is the **root** component of what will become a tree of nested components
as the application evolves.
as the application evolves.
使用HTML模板、CSS样式和单元测试定义`AppComponent`组件。
它是**根**组件,随着应用的成长它会成为一棵组件树的根节点。
</td>
</tr>
@ -352,9 +486,14 @@ Any files outside of this folder are meant to support building your app.
<td>
Defines `AppModule`, the [root module](guide/guide/appmodule "AppModule: the root module") that tells Angular how to assemble the application.
Right now it declares only the `AppComponent`.
Soon there will be more components to declare.
Defines `AppModule`, the [root module](guide/guide/appmodule "AppModule: the root module")
that tells Angular how to assemble the application.
Right now it declares only the `AppComponent`.
Soon there will be more components to declare.
定义`AppModule`,这个[根模块](guide/guide/appmodule "AppModule: 根模块")会告诉Angular如何组装该应用。
目前,它只声明了`AppComponent`。
稍后它还会声明更多组件。
</td>
</tr>
@ -370,6 +509,8 @@ Any files outside of this folder are meant to support building your app.
A folder where you can put images and anything else to be copied wholesale
when you build your application.
这个文件夹下你可以放图片等任何东西,在构建应用时,它们全都会拷贝到发布包中。
</td>
</tr>
@ -390,6 +531,13 @@ Any files outside of this folder are meant to support building your app.
or maybe different analytics tokens.
You might even use some mock services.
Either way, the CLI has you covered.
这个文件夹中包括为各个目标环境准备的文件,它们导出了一些应用中要用到的配置变量。
这些文件会在构建应用时被替换。
比如你可能在产品环境中使用不同的API端点地址或使用不同的统计Token参数。
甚至使用一些模拟服务。
所有这些CLI都替你考虑到了。
</td>
</tr>
@ -405,6 +553,9 @@ Any files outside of this folder are meant to support building your app.
Every site wants to look good on the bookmark bar.
Get started with your very own Angular icon.
每个网站都希望自己在书签栏中能好看一点。
请把它换成你自己的图标。
</td>
</tr>
@ -422,6 +573,10 @@ Any files outside of this folder are meant to support building your app.
Most of the time you'll never need to edit it.
The CLI automatically adds all `js` and `css` files when building your app so you
never need to add any `&lt;script&gt;` or `&lt;link&gt;` tags here manually.
这是别人访问你的网站是看到的主页面的HTML文件。
大多数情况下你都不用编辑它。
在构建应用时CLI会自动把所有`js`和`css`文件添加进去,所以你不必在这里手动添加任何 `<script>``<link>` 标签。
</td>
</tr>
@ -440,6 +595,11 @@ Any files outside of this folder are meant to support building your app.
and bootstraps the application's root module (`AppModule`) to run in the browser.
You can also use the [AOT compiler](guide/glossary#ahead-of-time-aot-compilation)
without changing any code by passing in `--aot` to `ng build` or `ng serve`.
这是应用的主要入口点。
使用[JIT compiler](guide/glossary#jit)编译器编译本应用,并[启动应用的根模块`AppModule`](guide/guide/appmodule#main "启动应用"),使其运行在浏览器中。
你还可以使用[AOT compiler](guide/glossary#ahead-of-time-aot-compilation)编译器,而不用修改任何代码 —— 只要给`ng build` 或 `ng serve` 传入 `--aot` 参数就可以了。
</td>
</tr>
@ -457,6 +617,11 @@ Any files outside of this folder are meant to support building your app.
Polyfills help normalize those differences.
You should be pretty safe with `core-js` and `zone.js`, but be sure to check out
the [Browser Support guide](guide/guide/browser-support) for more information.
不同的浏览器对Web标准的支持程度也不同。
填充库polyfill能帮我们把这些不同点进行标准化。
你只要使用`core-js` 和 `zone.js`通常就够了,不过你也可以查看[浏览器支持指南](guide/guide/browser-support)以了解更多信息。
</td>
</tr>
@ -473,6 +638,10 @@ Any files outside of this folder are meant to support building your app.
Your global styles go here.
Most of the time you'll want to have local styles in your components for easier maintenance,
but styles that affect all of your app need to be in a central place.
这里是你的全局样式。
大多数情况下,你会希望在组件中使用局部样式,以利于维护,不过那些会影响你整个应用的样式你还是需要集中存放在这里。
</td>
</tr>
@ -489,6 +658,10 @@ Any files outside of this folder are meant to support building your app.
This is the main entry point for your unit tests.
It has some custom configuration that might be unfamiliar, but it's not something you'll
need to edit.
这是单元测试的主要入口点。
它有一些你不熟悉的自定义配置,不过你并不需要编辑这里的任何东西。
</td>
</tr>
@ -505,6 +678,7 @@ Any files outside of this folder are meant to support building your app.
TypeScript compiler configuration for the Angular app (`tsconfig.app.json`)
and for the unit tests (`tsconfig.spec.json`).
TypeScript编译器的配置文件。`tsconfig.app.json`是为Angular应用准备的而`tsconfig.spec.json`是为单元测试准备的。
</td>
@ -516,10 +690,17 @@ Any files outside of this folder are meant to support building your app.
### The root folder
### 根目录
The `src/` folder is just one of the items inside the project's root folder.
Other files help you build, test, maintain, document, and deploy the app.
These files go in the root folder next to `src/`.
`src/`文件夹是项目的根文件夹之一。
其它文件是用来帮助你构建、测试、维护、文档化和发布应用的。它们放在根目录下,和`src/`平级。
<div class='filetree'>
@ -618,11 +799,27 @@ These files go in the root folder next to `src/`.
<tr>
<th>
File
<p>
File
</p>
<p>
文件
</p>
</th>
<th>
Purpose
<p>
Purpose
</p>
<p>
用途
</p>
</th>
</tr>
@ -640,6 +837,11 @@ These files go in the root folder next to `src/`.
They shouldn't be inside `src/` because e2e tests are really a separate app that
just so happens to test your main app.
That's also why they have their own `tsconfig.e2e.json`.
在`e2e/`下是端到端End-to-End测试。
它们不在`src/`下,是因为端到端测试实际上和应用是相互独立的,它只适用于测试你的应用而已。
这也就是为什么它会拥有自己的`tsconfig.json`。
</td>
</tr>
@ -655,6 +857,8 @@ These files go in the root folder next to `src/`.
`Node.js` creates this folder and puts all third party modules listed in
`package.json` inside of it.
`Node.js`创建了这个文件夹,并且把`package.json`中列举的所有第三方模块都放在其中。
</td>
</tr>
@ -672,6 +876,11 @@ These files go in the root folder next to `src/`.
In this file you can set several defaults and also configure what files are included
when your project is build.
Check out the official documentation if you want to know more.
Angular CLI的配置文件。
在这个文件中,我们可以设置一系列默认值,还可以配置项目编译时要包含的那些文件。
要了解更多,请参阅它的官方文档。
</td>
</tr>
@ -689,6 +898,10 @@ These files go in the root folder next to `src/`.
has the same basic configuration.
Most editors support an `.editorconfig` file.
See http://editorconfig.org for more information.
给你的编辑器看的一个简单配置文件,它用来确保参与你项目的每个人都具有基本的编辑器配置。
大多数的编辑器都支持`.editorconfig`文件,详情参见 http://editorconfig.org 。
</td>
</tr>
@ -703,6 +916,8 @@ These files go in the root folder next to `src/`.
Git configuration to make sure autogenerated files are not commited to source control.
一个Git的配置文件用来确保某些自动生成的文件不会被提交到源码控制系统中。
</td>
</tr>
@ -718,6 +933,9 @@ These files go in the root folder next to `src/`.
Unit test configuration for the [Karma test runner](https://karma-runner.github.io),
used when running `ng test`.
给[Karma](https://karma-runner.github.io)的单元测试配置,当运行`ng test`时会用到它。
</td>
</tr>
@ -733,6 +951,10 @@ These files go in the root folder next to `src/`.
`npm` configuration listing the third party packages your project uses.
You can also add your own [custom scripts](https://docs.npmjs.com/misc/scripts) here.
`npm`配置文件,其中列出了项目使用到的第三方依赖包。
你还可以在这里添加自己的[自定义脚本](https://docs.npmjs.com/misc/scripts)。
</td>
</tr>
@ -748,6 +970,8 @@ These files go in the root folder next to `src/`.
End-to-end test configuration for [Protractor](http://www.protractortest.org/),
used when running `ng e2e`.
给[Protractor](http://www.protractortest.org/)使用的端到端测试配置文件,当运行`ng e2e`的时候会用到它。
</td>
</tr>
@ -764,6 +988,10 @@ These files go in the root folder next to `src/`.
Basic documentation for your project, pre-filled with CLI command information.
Make sure to enhance it with project documentation so that anyone
checking out the repo can build your app!
项目的基础文档预先写入了CLI命令的信息。
别忘了用项目文档改进它,以便每个查看此仓库的人都能据此构建出你的应用。
</td>
</tr>
@ -778,6 +1006,8 @@ These files go in the root folder next to `src/`.
TypeScript compiler configuration for your IDE to pick up and give you helpful tooling.
TypeScript编译器的配置你的IDE会借助它来给你提供更好的帮助。
</td>
</tr>
@ -795,6 +1025,9 @@ These files go in the root folder next to `src/`.
[Codelyzer](http://codelyzer.com/), used when running `ng lint`.
Linting helps keep your code style consistent.
给[TSLint](https://palantir.github.io/tslint/)和[Codelyzer](http://codelyzer.com/)用的配置信息,当运行`ng lint`时会用到。
Lint功能可以帮你保持代码风格的统一。
</td>
</tr>
@ -809,9 +1042,14 @@ These files go in the root folder next to `src/`.
### Next Step
### 下一步
If you're new to Angular, continue on the
[learning path](guide/guide/learning-angular "Angular learning path").
You can skip the "Setup" step since you're already using the Angular CLI setup.
如果你刚刚开始使用Angular我们建议你遵循这个[学习路径](guide/guide/learning-angular "Angular 学习路径")。
你可以跳过“环境设置”一章,因为你已经在使用 Angular-CLI 设置好环境了。
</div>

View File

@ -1,35 +1,73 @@
@title
Component Interaction
组件通讯
@intro
Share information between different directives and components.
在不同的指令和组件之间共享信息
@description
{@a top}
This cookbook contains recipes for common component communication scenarios
in which two or more components share information.
本烹饪宝典包含了常见的组件通讯场景,也就是让两个或多个组件之间共享信息的方法。
<div class="l-sub-section">
For an in-depth look at each fundamental concepts in component communication, we can find detailed description and
samples in the [Component Communication]() document.
要深入了解组件通讯的各个基本概念,在[组件通讯]()文档中可以找到详细的描述和例子。
</div>
{@a toc}
# Contents
# 目录
* [Pass data from parent to child with input binding](guide/component-communication#parent-to-child)
[使用输入绑定把数据从父组件传给子组件](guide/component-communication#parent-to-child)
* [Intercept input property changes with a setter](guide/component-communication#parent-to-child-setter)
[使用赋值器setter拦截输入属性的变化](guide/component-communication#parent-to-child-setter)
* [Intercept input property changes with `ngOnChanges()`](guide/component-communication#parent-to-child-on-changes)
[使用`ngOnChanges()`拦截输入属性的变化](guide/component-communication#parent-to-child-on-changes)
* [Parent calls an `@ViewChild()`](guide/component-communication#parent-to-view-child)
[在父组件中调用`@ViewChild()`](guide/component-communication#parent-to-view-child)
* [Parent and children communicate via a service](guide/component-communication#bidirectional-service)
[通过服务进行父子通讯](guide/component-communication#bidirectional-service)
**See the <live-example name="cb-component-communication"></live-example>**.
**参见<live-example name="cb-component-communication"></live-example>**。
{@a parent-to-child}
## Pass data from parent to child with input binding
`HeroChildComponent` has two ***input properties***,
## 通过输入型绑定把数据从父组件传到子组件。
`HeroChildComponent` has two ***input properties***,
typically adorned with [@Input decorations](guide/template-syntax#inputs-outputs).
`HeroChildComponent` 有两个***输入型属性***,它们通常带[@Input装饰器](guide/template-syntax#inputs-outputs)。
<code-example path="cb-component-communication/src/app/hero-child.component.ts" title="cb-component-communication/src/app/hero-child.component.ts">
@ -39,9 +77,12 @@ typically adorned with [@Input decorations](guide/template-syntax#inputs-outputs
The second `@Input` aliases the child component property name `masterName` as `'master'`.
The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater,
binding its `master` string property to the child's `master` alias,
and each iteration's `hero` instance to the child's `hero` property.
第二个`@Input`为子组件的属性名`masterName`指定一个别名`master`(译者注:不推荐为起别名,请参见风格指南).
The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater,
binding its `master` string property to the child's `master` alias, and each iteration's `hero` instance to the child's `hero` property.
父组件`HeroParentComponent`把子组件的`HeroChildComponent`放到`*ngFor`循环器中,把自己的`master`字符串属性绑定到子组件的`master`别名上,并把每个循环的`hero`实例绑定到子组件的`hero`属性。
<code-example path="cb-component-communication/src/app/hero-parent.component.ts" title="cb-component-communication/src/app/hero-parent.component.ts">
@ -52,6 +93,8 @@ and each iteration's `hero` instance to the child's `hero` property.
The running application displays three heroes:
运行应用程序会显示三个英雄:
<figure class='image-display'>
<img src="assets/images/cookbooks/component-communication/parent-to-child.png" alt="Parent-to-child"></img>
@ -61,8 +104,12 @@ The running application displays three heroes:
### Test it
### 测试
E2E test that all children were instantiated and displayed as expected:
端到端测试,用于确保所有的子组件都像所期待的那样被初始化并显示出来。
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child" title="cb-component-communication/e2e-spec.ts">
@ -72,14 +119,22 @@ E2E test that all children were instantiated and displayed as expected:
[Back to top](guide/component-communication#top)
[回到顶部](guide/component-communication#top)
{@a parent-to-child-setter}
## Intercept input property changes with a setter
## 通过setter截听输入属性值的变化
Use an input property setter to intercept and act upon a value from the parent.
The setter of the `name` input property in the child `NameChildComponent`
trims the whitespace from a name and replaces an empty value with default text.
使用一个输入属性的setter以拦截父组件中值的变化并采取行动。
The setter of the `name` input property in the child `NameChildComponent`
trims the whitespace from a name and replaces an empty value with default text.
子组件`NameChildComponent`的输入属性`name`上的这个setter会trim掉名字里的空格并把空值替换成默认字符串。
<code-example path="cb-component-communication/src/app/name-child.component.ts" title="cb-component-communication/src/app/name-child.component.ts">
@ -90,6 +145,8 @@ trims the whitespace from a name and replaces an empty value with default text.
Here's the `NameParentComponent` demonstrating name variations including a name with all spaces:
下面的`NameParentComponent`展示了各种名字的处理方式,包括一个全是空格的名字。
<code-example path="cb-component-communication/src/app/name-parent.component.ts" title="cb-component-communication/src/app/name-parent.component.ts">
@ -105,8 +162,12 @@ Here's the `NameParentComponent` demonstrating name variations including a name
### Test it
### 测试
E2E tests of input property setter with empty and non-empty names:
端到端测试输入属性的setter分别使用空名字和非空名字。
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child-setter" title="cb-component-communication/e2e-spec.ts">
@ -116,26 +177,40 @@ E2E tests of input property setter with empty and non-empty names:
[Back to top](guide/component-communication#top)
[回到顶部](guide/component-communication#top)
{@a parent-to-child-on-changes}
## Intercept input property changes with *ngOnChanges()*
## 通过*ngOnChanges()*来截听输入属性值的变化
Detect and act upon changes to input property values with the `ngOnChanges()` method of the `OnChanges` lifecycle hook interface.
使用`OnChanges`生命周期钩子接口的`ngOnChanges()`方法来监测输入属性值的变化并做出回应。
<div class="l-sub-section">
You may prefer this approach to the property setter when watching multiple, interacting input properties.
当需要监视多个、交互式输入属性的时候本方法比用属性的setter更合适。
Learn about `ngOnChanges()` in the [LifeCycle Hooks](guide/lifecycle-hooks) chapter.
学习关于`ngOnChanges()`的更多知识,参见[生命周期钩子](guide/lifecycle-hooks)一章。
</div>
This `VersionChildComponent` detects changes to the `major` and `minor` input properties and composes a log message reporting these changes:
这个`VersionChildComponent`会监测输入属性`major`和`minor`的变化,并把这些变化编写成日志以报告这些变化。
<code-example path="cb-component-communication/src/app/version-child.component.ts" title="cb-component-communication/src/app/version-child.component.ts">
@ -145,6 +220,8 @@ This `VersionChildComponent` detects changes to the `major` and `minor` input pr
The `VersionParentComponent` supplies the `minor` and `major` values and binds buttons to methods that change them.
`VersionParentComponent`提供`minor`和`major`值,把修改它们值的方法绑定到按钮上。
<code-example path="cb-component-communication/src/app/version-parent.component.ts" title="cb-component-communication/src/app/version-parent.component.ts">
@ -154,6 +231,8 @@ The `VersionParentComponent` supplies the `minor` and `major` values and binds b
Here's the output of a button-pushing sequence:
下面是点击按钮的结果。
<figure class='image-display'>
<img src="assets/images/cookbooks/component-communication/parent-to-child-on-changes.gif" alt="Parent-to-child-onchanges"></img>
@ -163,9 +242,13 @@ Here's the output of a button-pushing sequence:
### Test it
Test that ***both*** input properties are set initially and that button clicks trigger
### 测试
Test that ***both*** input properties are set initially and that button clicks trigger
the expected `ngOnChanges` calls and values:
测试确保***这两个***输入属性值都被初始化了,当点击按钮后,`ngOnChanges`应该被调用,属性的值也符合预期。
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child-onchanges" title="cb-component-communication/e2e-spec.ts">
@ -175,16 +258,24 @@ the expected `ngOnChanges` calls and values:
[Back to top](guide/component-communication#top)
[回到顶部](guide/component-communication#top)
{@a child-to-parent}
## Parent listens for child event
The child component exposes an `EventEmitter` property with which it `emits` events when something happens.
## 父组件监听子组件的事件
The child component exposes an `EventEmitter` property with which it `emits`events when something happens.
The parent binds to that event property and reacts to those events.
The child's `EventEmitter` property is an ***output property***,
子组件暴露一个`EventEmitter`属性,当事件发生时,子组件利用该属性`emits`(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。
The child's `EventEmitter` property is an ***output property***,
typically adorned with an [@Output decoration](guide/template-syntax#inputs-outputs)
as seen in this `VoterComponent`:
子组件的`EventEmitter`属性是一个**输出属性**,通常带有[@Output装饰器](guide/template-syntax#inputs-outputs),就像在`VoterComponent`中看到的。
<code-example path="cb-component-communication/src/app/voter.component.ts" title="cb-component-communication/src/app/voter.component.ts">
@ -193,11 +284,15 @@ The child's `EventEmitter` property is an ***output property***,
Clicking a button triggers emission of a `true` or `false`, the boolean *payload*.
Clicking a button triggers emission of a `true` or `false` ,the boolean *payload*.
The parent `VoteTakerComponent` binds an event handler called `onVoted()` that responds to the child event
点击按钮会触发`true`或`false`(布尔型*有效载荷*)的事件。
The parent `VoteTakerComponent` binds an event handler called`onVoted()` that responds to the child event
payload `$event` and updates a counter.
父组件`VoteTakerComponent`绑定了一个事件处理器(`onVoted()`),用来响应子组件的事件(`$event`)并更新一个计数器。
<code-example path="cb-component-communication/src/app/votetaker.component.ts" title="cb-component-communication/src/app/votetaker.component.ts">
@ -205,9 +300,11 @@ payload `$event` and updates a counter.
The framework passes the event argument&mdash;represented by `$event`&mdash;to the handler method,
The framework passes the event argument &mdash; represented by `$event` &mdash; to the handler method,
and the method processes it:
框架(Angular)把事件参数(用`$event`表示)传给事件处理方法,这个方法会处理:
<figure class='image-display'>
<img src="assets/images/cookbooks/component-communication/child-to-parent.gif" alt="Child-to-parent"></img>
@ -217,8 +314,12 @@ and the method processes it:
### Test it
### 测试
Test that clicking the *Agree* and *Disagree* buttons update the appropriate counters:
测试确保点击*Agree*和*Disagree*按钮时,计数器被正确更新。
<code-example path="cb-component-communication/e2e-spec.ts" region="child-to-parent" title="cb-component-communication/e2e-spec.ts">
@ -228,20 +329,27 @@ Test that clicking the *Agree* and *Disagree* buttons update the appropriate cou
[Back to top](guide/component-communication#top)
[回到顶部](guide/component-communication#top)
## Parent interacts with child via *local variable*
## 父组件与子组件通过*本地变量*互动
A parent component cannot use data binding to read child properties
or invoke child methods. You can do both
or invoke child methods. You can do both
by creating a template reference variable for the child element
and then reference that variable *within the parent template*
as seen in the following example.
{@a countdown-timer-example}
The following is a child `CountdownTimerComponent` that repeatedly counts down to zero and launches a rocket.
It has `start` and `stop` methods that control the clock and it displays a
countdown status message in its own template.
父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法,如下例所示。
{@a countdown-timer-example}
The following is a child `CountdownTimerComponent` that repeatedly counts down to zero and launches a rocket. It has `start` and `stop` methods that control the clock and it displays a countdown status message in its own template.
子组件`CountdownTimerComponent`进行倒计时,归零时发射一个导弹。`start`和`stop`方法负责控制时钟并在模板里显示倒计时的状态信息。
<code-example path="cb-component-communication/src/app/countdown-timer.component.ts" title="cb-component-communication/src/app/countdown-timer.component.ts">
@ -249,7 +357,9 @@ countdown status message in its own template.
The `CountdownLocalVarParentComponent` that hosts the timer component is as follows:
The `CountdownLocalVarParentComponent` that hosts the timer componentis as follows:
让我们来看看计时器组件的宿主组件`CountdownLocalVarParentComponent`。
<code-example path="cb-component-communication/src/app/countdown-parent.component.ts" region="lv" title="cb-component-communication/src/app/countdown-parent.component.ts">
@ -261,15 +371,23 @@ The `CountdownLocalVarParentComponent` that hosts the timer component is as foll
The parent component cannot data bind to the child's
`start` and `stop` methods nor to its `seconds` property.
父组件不能通过数据绑定使用子组件的`start`和`stop`方法,也不能访问子组件的`seconds`属性。
You can place a local variable, `#timer`, on the tag `<countdown-timer>` representing the child component.
That gives you a reference to the child component and the ability to access
*any of its properties or methods* from within the parent template.
把本地变量(`#timer`)放到(`<countdown-timer>`)标签中,用来代表子组件。这样父组件的模板就得到了子组件的引用,于是可以在父组件的模板中访问子组件的所有属性和方法。
This example wires parent buttons to the child's `start` and `stop` and
uses interpolation to display the child's `seconds` property.
在这个例子中,我们把父组件的按钮绑定到子组件的`start`和`stop`方法,并用插值表达式来显示子组件的`seconds`属性。
Here we see the parent and child working together.
下面是父组件和子组件一起工作时的效果。
<figure class='image-display'>
<img src="assets/images/cookbooks/component-communication/countdown-timer-anim.gif" alt="countdown timer"></img>
@ -282,10 +400,14 @@ Here we see the parent and child working together.
### Test it
### 测试
Test that the seconds displayed in the parent template
match the seconds displayed in the child's status message.
Test also that clicking the *Stop* button pauses the countdown timer:
测试确保在父组件模板中显示的秒数和子组件状态信息里的秒数同步。它还会点击*Stop*按钮来停止倒计时:
<code-example path="cb-component-communication/e2e-spec.ts" region="countdown-timer-tests" title="cb-component-communication/e2e-spec.ts">
@ -295,25 +417,39 @@ Test also that clicking the *Stop* button pauses the countdown timer:
[Back to top](guide/component-communication#top)
[回到顶部](guide/component-communication#top)
{@a parent-to-view-child}
## Parent calls an _@ViewChild()_
## 父组件调用*@ViewChild()*
The *local variable* approach is simple and easy. But it is limited because
the parent-child wiring must be done entirely within the parent template.
The parent component *itself* has no access to the child.
这个*本地变量*方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。
You can't use the *local variable* technique if an instance of the parent component *class*
must read or write child component values or must call child component methods.
如果父组件的*类*需要读取子组件的属性值或调用子组件的方法,就不能使用*本地变量*方法。
When the parent component *class* requires that kind of access,
***inject*** the child component into the parent as a *ViewChild*.
当父组件*类*需要这种访问时,可以把子组件作为*ViewChild****注入***到父组件里面。
The following example illustrates this technique with the same
[Countdown Timer](guide/component-communication#countdown-timer-example) example.
Neither its appearance nor its behavior will change.
The child [CountdownTimerComponent](guide/component-communication#countdown-timer-example) is the same as well.
下面的例子用与[倒计时](guide/component-communication#countdown-timer-example)相同的范例来解释这种技术。
我们没有改变它的外观或行为。子组件[CountdownTimerComponent](guide/component-communication#countdown-timer-example)也和原来一样。
<div class="l-sub-section">
@ -321,12 +457,18 @@ The child [CountdownTimerComponent](guide/component-communication#countdown-time
The switch from the *local variable* to the *ViewChild* technique
is solely for the purpose of demonstration.
由*本地变量*切换到*ViewChild*技术的唯一目的就是做示范。
</div>
Here is the parent, `CountdownViewChildParentComponent`:
下面是父组件`CountdownViewChildParentComponent`:
<code-example path="cb-component-communication/src/app/countdown-parent.component.ts" region="vc" title="cb-component-communication/src/app/countdown-parent.component.ts">
</code-example>
@ -335,46 +477,78 @@ Here is the parent, `CountdownViewChildParentComponent`:
It takes a bit more work to get the child view into the parent component *class*.
把子组件的视图插入到父组件类需要做一点额外的工作。
First, you have to import references to the `ViewChild` decorator and the `AfterViewInit` lifecycle hook.
首先,你要使用`ViewChild`装饰器导入这个引用,并挂上`AfterViewInit`生命周期钩子。
Next, inject the child `CountdownTimerComponent` into the private `timerComponent` property
via the `@ViewChild` property decoration.
The `#timer` local variable is gone from the component metadata.
Instead, bind the buttons to the parent component's own `start` and `stop` methods and
接着,通过`@ViewChild`属性装饰器,将子组件`CountdownTimerComponent`注入到私有属性`timerComponent`里面。
The `#timer` local variable is gone from the component metadata.
Instead , bind the buttons to the parent component's own `start` and `stop` methods and
present the ticking seconds in an interpolation around the parent component's `seconds` method.
组件元数据里就不再需要`#timer`本地变量了。而是把按钮绑定到父组件自己的`start`和`stop`方法,使用父组件的`seconds`方法的插值表达式来展示秒数变化。
These methods access the injected timer component directly.
这些方法可以直接访问被注入的计时器组件。
The `ngAfterViewInit()` lifecycle hook is an important wrinkle.
The timer component isn't available until *after* Angular displays the parent view.
So it displays `0` seconds initially.
`ngAfterViewInit()`生命周期钩子是非常重要的一步。被注入的计时器组件只有在Angular显示了父组件视图之后才能访问所以我们先把秒数显示为0.
Then Angular calls the `ngAfterViewInit` lifecycle hook at which time it is *too late*
to update the parent view's display of the countdown seconds.
Angular's unidirectional data flow rule prevents updating the parent view's
in the same cycle. The app has to *wait one turn* before it can display the seconds.
然后Angular会调用`ngAfterViewInit`生命周期钩子但这时候再更新父组件视图的倒计时就已经太晚了。Angular的单向数据流规则会阻止在同一个周期内更新父组件视图。我们在显示秒数之前会被迫*再等一轮*。
Use `setTimeout()` to wait one tick and then revise the `seconds()` method so
that it takes future values from the timer component.
使用`setTimeout()`来等下一轮,然后改写`seconds()`方法,这样它接下来就会从注入的这个计时器组件里获取秒数的值。
### Test it
### 测试
Use [the same countdown timer tests](guide/component-communication#countdown-tests) as before.
使用和之前[一样的倒计时测试](guide/component-communication#countdown-tests)。
[Back to top](guide/component-communication#top)
[回到顶部](guide/'#top')
{@a bidirectional-service}
## Parent and children communicate via a service
A parent component and its children share a service whose interface enables bi-directional communication
*within the family*.
## 父组件和子组件通过服务来通讯
The scope of the service instance is the parent component and its children.
A parent component and its children share a service whose interface enables bi-directional communication *within the family*.
父组件和它的子组件共享同一个服务,利用该服务*在家庭内部*实现双向通讯。
The scope of the service instance is the parent component and its children.
Components outside this component subtree have no access to the service or their communications.
该服务实例的作用域被限制在父组件和其子组件内。这个组件子树之外的组件将无法访问该服务或者与它们通讯。
This `MissionService` connects the `MissionControlComponent` to multiple `AstronautComponent` children.
这个`MissionService`把`MissionControlComponent`和多个`AstronautComponent`子组件连接起来。
<code-example path="cb-component-communication/src/app/mission.service.ts" title="cb-component-communication/src/app/mission.service.ts">
@ -385,6 +559,8 @@ This `MissionService` connects the `MissionControlComponent` to multiple `Astron
The `MissionControlComponent` both provides the instance of the service that it shares with its children
(through the `providers` metadata array) and injects that instance into itself through its constructor:
`MissionControlComponent`提供服务的实例,并将其共享给它的子组件(通过`providers`元数据数组),子组件可以通过构造函数将该实例注入到自身。
<code-example path="cb-component-communication/src/app/missioncontrol.component.ts" title="cb-component-communication/src/app/missioncontrol.component.ts">
@ -395,6 +571,8 @@ The `MissionControlComponent` both provides the instance of the service that it
The `AstronautComponent` also injects the service in its constructor.
Each `AstronautComponent` is a child of the `MissionControlComponent` and therefore receives its parent's service instance:
`AstronautComponent`也通过自己的构造函数注入该服务。由于每个`AstronautComponent`都是`MissionControlComponent`的子组件,所以它们获取到的也是父组件的这个服务实例。
<code-example path="cb-component-communication/src/app/astronaut.component.ts" title="cb-component-communication/src/app/astronaut.component.ts">
@ -411,9 +589,15 @@ This is a memory-leak guard step. There is no actual risk in this app because th
lifetime of a `AstronautComponent` is the same as the lifetime of the app itself.
That *would not* always be true in a more complex application.
注意,这个例子保存了`subscription`变量,并在`AstronautComponent`被销毁时调用`unsubscribe()`退订。
这是一个用于防止内存泄漏的保护措施。实际上,在这个应用程序中并没有这个风险,因为`AstronautComponent`的生命期和应用程序的生命期一样长。但在更复杂的应用程序环境中就不一定了。
You don't add this guard to the `MissionControlComponent` because, as the parent,
it controls the lifetime of the `MissionService`.
不需要在`MissionControlComponent`中添加这个保护措施,因为它作为父组件,控制着`MissionService`的生命期。
</div>
@ -422,6 +606,8 @@ The *History* log demonstrates that messages travel in both directions between
the parent `MissionControlComponent` and the `AstronautComponent` children,
facilitated by the service:
*History*日志证明了:在父组件`MissionControlComponent`和子组件`AstronautComponent`之间,信息通过该服务实现了双向传递。
<figure class='image-display'>
<img src="assets/images/cookbooks/component-communication/bidirectional-service.gif" alt="bidirectional-service"></img>
@ -431,9 +617,13 @@ facilitated by the service:
### Test it
### 测试
Tests click buttons of both the parent `MissionControlComponent` and the `AstronautComponent` children
and verify that the history meets expectations:
测试确保点击父组件`MissionControlComponent`和子组件`AstronautComponent`两个的组件的按钮时,*History*日志和预期的一样。
<code-example path="cb-component-communication/e2e-spec.ts" region="bidirectional-service" title="cb-component-communication/e2e-spec.ts">
@ -441,4 +631,6 @@ and verify that the history meets expectations:
[Back to top](guide/component-communication#top)
[Back to top](guide/component-communication#top)
[回到顶部](guide/component-communication#top)

View File

@ -1,8 +1,8 @@
@title
Component Styles
组件样式
@intro
Learn how to apply CSS styles to components.
学习如何给组件应用 CSS 样式。
@description
@ -12,34 +12,71 @@ Angular applications are styled with standard CSS. That means you can apply
everything you know about CSS stylesheets, selectors, rules, and media queries
directly to Angular applications.
Angular 应用使用标准的 CSS 来设置样式。这意味着我们可以把关于 CSS
的那些知识和技能直接用于我们的 Angular 程序中,例如:样式表、选择器、规则以及媒体查询等。
Additionally, Angular can bundle *component styles*
with components, enabling a more modular design than regular stylesheets.
另外Angular 还能把*组件样式*捆绑在我们的组件上,以实现比标准样式表更加模块化的设计。
This page describes how to load and apply these component styles.
在本章中,我们将学到如何加载和使用这些*组件样式*。
## Table Of Contents
## 目录
* [Using component styles](guide/component-styles#using-component-styles)
[使用组件样式](guide/component-styles#using-component-styles)
* [Special selectors](guide/component-styles#special-selectors)
[特殊的选择器](guide/component-styles#special-selectors)
* [Loading styles into components](guide/component-styles#loading-styles)
[把样式加载进组件](guide/component-styles#loading-styles)
* [Controlling view encapsulation: native, emulated, and none](guide/component-styles#view-encapsulation)
[控制视图的封装模式:仿真 (Emulated)、原生 (Native) 或无 (None)](guide/component-styles#view-encapsulation)
* [Appendix 1: Inspecting the CSS generated in emulated view encapsulation](guide/component-styles#inspect-generated-css)
[附录 1审查生成的运行时组件样式](guide/component-styles#inspect-generated-css)
* [Appendix 2: Loading styles with relative URLs](guide/component-styles#relative-urls)
[附录 2使用相对 URL 加载样式](guide/component-styles#relative-urls)
You can run the <live-example></live-example> in Plunker and download the code from there.
你可以在Plunker上运行本章这些代码的<live-example></live-example>并下载这些代码。
## Using component styles
## 使用组件样式
For every Angular component you write, you may define not only an HTML template,
but also the CSS styles that go with that template,
specifying any selectors, rules, and media queries that you need.
对于我们写的每个 Angular 组件来说,除了定义 HTML 模板之外,我们还要定义用于模板的 CSS 样式、
指定任意的选择器、规则和媒体查询。
One way to do this is to set the `styles` property in the component metadata.
The `styles` property takes an array of strings that contain CSS code.
Usually you give it one string, as in the following example:
实现方式之一,是在组件的元数据中设置`styles`属性。
`styles`属性可以接受一个包含 CSS 代码的字符串数组。
通常我们只给它一个字符串就行了,如同下例:
<code-example path="component-styles/src/app/hero-app.component.ts" title="src/app/hero-app.component.ts" linenums="false">
@ -52,17 +89,36 @@ of that component. The `h1` selector in the preceding example applies only to th
in the template of `HeroAppComponent`. Any `<h1>` elements elsewhere in
the application are unaffected.
我们放在组件样式中的选择器,*只会应用在组件自身的模板中*。上面这个例子中的`h1`选择器只会对
`HeroAppComponent`模板中的`<h1>`标签生效,而对应用中其它地方的`<h1>`元素毫无影响。
This is a big improvement in modularity compared to how CSS traditionally works.
这种模块化相对于 CSS 的传统工作方式是一个巨大的改进。
* You can use the CSS class names and selectors that make the most sense in the context of each component.
可以使用对每个组件最有意义的 CSS 类名和选择器。
* Class names and selectors are local to the component and don't collide with
classes and selectors used elsewhere in the application.
类名和选择器是仅属于组件内部的,它不会和应用中其它地方的类名和选择器出现冲突。
* Changes to styles elsewhere in the application don't affect the component's styles.
我们组件的样式*不会*因为别的地方修改了样式而被意外改变。
* You can co-locate the CSS code of each component with the TypeScript and HTML code of the component,
which leads to a neat and tidy project structure.
我们可以让每个组件的 CSS 代码和它的 TypeScript、HTML 代码放在一起,这将促成清爽整洁的项目结构。
* You can change or remove component CSS code without searching through the
whole application to find where else the code is used.
将来我们可以修改或移除组件的 CSS 代码,而不用遍历整个应用来看它有没有被别处用到,只要看看当前组件就可以了。
{@a special-selectors}
@ -70,16 +126,22 @@ This is a big improvement in modularity compared to how CSS traditionally works.
## Special selectors
## 特殊的选择器
Component styles have a few special *selectors* from the world of shadow DOM style scoping
(described in the [CSS Scoping Module Level 1](https://www.w3.org/TR/css-scoping-1) page on the
[W3C](https://www.w3.org) site).
The following sections describe these selectors.
组件样式中有一些从影子(Shadow) DOM 样式范围领域(记录在[W3C](https://www.w3.org)的[CSS Scoping Module Level 1](https://www.w3.org/TR/css-scoping-1)中) 引入的特殊*选择器*
### :host
Use the `:host` pseudo-class selector to target styles in the element that *hosts* the component (as opposed to
targeting elements *inside* the component's template).
使用`:host`伪类选择器,用来选择组件*宿主*元素中的元素(相对于组件模板*内部*的元素)。
<code-example path="component-styles/src/app/hero-details.component.css" region="host" title="src/app/hero-details.component.css" linenums="false">
@ -91,10 +153,17 @@ The `:host` selector is the only way to target the host element. You can't reach
the host element from inside the component with other selectors because it's not part of the
component's own template. The host element is in a parent component's template.
Use the *function form* to apply host styles conditionally by
这是我们能以宿主元素为目标的*唯一*方式。除此之外,我们将没办法指定它,
因为宿主不是组件自身模板的一部分,而是父组件模板的一部分。
Use the *function form* to apply host styles conditionally by
including another selector inside parentheses after `:host`.
要把宿主样式作为条件,就要像*函数*一样把其它选择器放在`:host`后面的括号中。
The next example targets the host element again, but only when it also has the `active` CSS class.
在下一个例子中,我们又一次把宿主元素作为目标,但是只有当它同时带有`active` CSS 类的时候才会生效。
<code-example path="component-styles/src/app/hero-details.component.css" region="hostfunction" title="src/app/hero-details.component.css" linenums="false">
@ -109,12 +178,20 @@ Sometimes it's useful to apply styles based on some condition *outside* of a com
For example, a CSS theme class could be applied to the document `<body>` element, and
you want to change how your component looks based on that.
有时候,基于某些来自组件视图*外部*的条件应用样式是很有用的。
例如,在文档的`<body>`元素上可能有一个用于表示样式主题 (theme) 的 CSS 类,而我们应当基于它来决定组件的样式。
Use the `:host-context()` pseudo-class selector, which works just like the function
form of `:host()`. The `:host-context()` selector looks for a CSS class in any ancestor of the component host element,
up to the document root. The `:host-context()` selector is useful when combined with another selector.
这时可以使用`:host-context()`伪类选择器。它也以类似`:host()`形式使用。它在当前组件宿主元素的*祖先节点*中查找 CSS 类,
直到文档的根节点为止。在与其它选择器组合使用时,它非常有用。
The following example applies a `background-color` style to all `<h2>` elements *inside* the component, only
if some ancestor element has the CSS class `theme-light`.
在下面的例子中,只有当某个祖先元素有 CSS 类`theme-light`时,我们才会把`background-color`样式应用到组件*内部*的所有`<h2>`元素中。
<code-example path="component-styles/src/app/hero-details.component.css" region="hostcontext" title="src/app/hero-details.component.css" linenums="false">
@ -127,13 +204,20 @@ if some ancestor element has the CSS class `theme-light`.
Component styles normally apply only to the HTML in the component's own template.
组件样式通常只会作用于组件自身的 HTML 上。
Use the `/deep/` selector to force a style down through the child component tree into all the child component views.
The `/deep/` selector works to any depth of nested components, and it applies to both the view
children and content children of the component.
我们可以使用`/deep/`选择器,来强制一个样式对各级子组件的视图也生效,它*不但作用于组件的子视图,也会作用于组件的内容*。
The following example targets all `<h3>` elements, from the host element down
through this component to all of its child elements in the DOM.
在这个例子中,我们以所有的`<h3>`元素为目标,从宿主元素到当前元素再到 DOM 中的所有子元素:
<code-example path="component-styles/src/app/hero-details.component.css" region="deep" title="src/app/hero-details.component.css" linenums="false">
</code-example>
@ -142,6 +226,8 @@ through this component to all of its child elements in the DOM.
The `/deep/` selector also has the alias `>>>`. You can use either interchangeably.
`/deep/`选择器还有一个别名`>>>`。我们可以任意交替使用它们。
<div class="alert is-important">
@ -151,6 +237,10 @@ Use the `/deep/` and `>>>` selectors only with *emulated* view encapsulation.
Emulated is the default and most commonly used view encapsulation. For more information, see the
[Controlling view encapsulation](guide/component-styles#view-encapsulation) section.
`/deep/`和`>>>`选择器只能被用在**仿真 (emulated) **模式下。
这种方式是默认值,也是用得最多的方式。
更多信息,见[控制视图封装模式](guide/component-styles#view-encapsulation)一节。
</div>
@ -162,19 +252,38 @@ Emulated is the default and most commonly used view encapsulation. For more info
## Loading styles into components
## 把样式加载进组件中
There are several ways to add styles to a component:
有几种方式把样式加入组件:
* By setting `styles` or `styleUrls` metadata.
设置`styles`或`styleUrls`元数据
* Inline in the template HTML.
内联在模板的 HTML 中
* With CSS imports.
通过 CSS 文件导入
The scoping rules outlined earlier apply to each of these loading patterns.
上述作用域规则对所有这些加载模式都适用。
### Styles in metadata
### 元数据中的样式
You can add a `styles` array property to the `@Component` decorator.
Each string in the array (usually just one string) defines the CSS.
我们可以给`@Component`装饰器添加一个`styles`数组型属性。
这个数组中的每一个字符串(通常也只有一个)定义一份 CSS。
<code-example path="component-styles/src/app/hero-app.component.ts" title="src/app/hero-app.component.ts">
@ -184,9 +293,13 @@ Each string in the array (usually just one string) defines the CSS.
### Style URLs in metadata
### 元数据中指定样式表的URL
You can load styles from external CSS files by adding a `styleUrls` attribute
into a component's `@Component` decorator:
通过在组件的`@Component`装饰器中添加`styleUrls`属性我们可以从外部CSS文件中加载样式
<code-example path="component-styles/src/app/hero-details.component.ts" region="styleurls" title="src/app/hero-details.component.ts">
@ -204,6 +317,10 @@ The style file URL is *not* relative to the component file.
That's why the example URL begins `src/app/`.
To specify a URL relative to the component file, see [Appendix 2](guide/component-styles#relative-urls).
URL是***相对于应用程序根目录的***,它通常是应用的宿主页面`index.html`所在的地方。
这个样式文件的 URL *不是*相对于组件文件的。这就是为什么范例中的 URL 用`src/app/`开头。
参见[附录 2](guide/component-styles#relative-urls) 来了解如何指定相对于组件文件的 URL。
</div>
@ -216,13 +333,22 @@ To specify a URL relative to the component file, see [Appendix 2](guide/componen
If you use module bundlers like Webpack, you can also use the `styles` attribute
to load styles from external files at build time. You could write:
像 Webpack 这类模块打包器的用户可能会使用`styles`属性来在构建时从外部文件中加载样式。它们可能这样写:
`styles: [require('my.component.css')]`
Set the `styles` property, not the `styleUrls` property. The module
Set the `styles` property, notthe `styleUrls` property. The module
bundler loads the CSS strings, not Angular.
Angular sees the CSS strings only after the bundler loads them.
To Angular, it's as if you wrote the `styles` array by hand.
For information on loading CSS in this manner, refer to the module bundler's documentation.
Angular sees the CSS strings onlyafter the bundler loads them.
To Angular, it 's as if you wrote the `styles` array by hand.
For information on
loading CSS in this manner, refer to the module bundler's documentation.
注意,这时候我们是在设置`styles`属性,**而不是**`styleUrls`属性!
是模块打包器在加载 CSS 字符串,而不是 Angular。
Angular 看到的只是打包器加载它们之后的 CSS 字符串。
对 Angular 来说,这跟我们手写了`styles`数组没有任何区别。
要了解这种 CSS 加载方式的更多信息,请参阅相应模块打包器的文档。
</div>
@ -231,9 +357,13 @@ For information on loading CSS in this manner, refer to the module bundler's doc
### Template inline styles
### 模板内联样式
You can embed styles directly into the HTML template by putting them
inside `<style>` tags.
我们也可以在组件的 HTML 模板中嵌入`<style>`
<code-example path="component-styles/src/app/hero-controls.component.ts" region="inlinestyles" title="src/app/hero-controls.component.ts">
@ -243,10 +373,16 @@ inside `<style>` tags.
### Template link tags
### 模板中的link标签
You can also embed `<link>` tags into the component's HTML template.
我们也可以在组件的 HTML 模板中嵌入`<link>`标签。
As with `styleUrls`, the link tag's `href` URL is relative to the
application root, not the component file.
application root, not the component file.
像`styleUrls`标签一样,这个 link 标签的`href`指向的 URL 也是相对于应用的根目录的,而不是组件文件。
<code-example path="component-styles/src/app/hero-team.component.ts" region="stylelink" title="src/app/hero-team.component.ts">
@ -261,9 +397,14 @@ You can also import CSS files into the CSS files using the standard CSS `@import
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
on the [MDN](https://developer.mozilla.org) site.
我们还可以利用标准的 CSS [`@import`规则](https://developer.mozilla.org/en/docs/Web/CSS/@import)来把其它
CSS 文件导入到我们的 CSS 文件中。
In this case, the URL is relative to the CSS file into which you're importing.
在*这种*情况下URL 是相对于我们执行导入操作的 CSS 文件的。
<code-example path="component-styles/src/app/hero-details.component.css" region="import" title="src/app/hero-details.component.css (excerpt)">
@ -277,30 +418,49 @@ In this case, the URL is relative to the CSS file into which you're importing.
## Controlling view encapsulation: native, emulated, and none
## 控制视图的封装模式:原生 (Native)、仿真 (Emulated) 和无 (None)
As discussed earlier, component CSS styles are encapsulated into the component's view and don't
affect the rest of the application.
像上面讨论过的一样,组件的 CSS 样式被封装进了自己的视图中,而不会影响到应用程序的其它部分。
To control how this encapsulation happens on a *per
component* basis, you can set the *view encapsulation mode* in the component metadata.
Choose from the following modes:
通过在组件的元数据上设置*视图封装模式*,我们可以分别控制*每个组件*的封装模式。
可选的封装模式一共有如下几种:
* `Native` view encapsulation uses the browser's native shadow DOM implementation (see
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
on the [MDN](https://developer.mozilla.org) site)
to attach a shadow DOM to the component's host element, and then puts the component
view inside that shadow DOM. The component's styles are included within the shadow DOM.
`Native`模式使用浏览器原生的 [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
实现来为组件的宿主元素附加一个 Shadow DOM。组件的样式被包裹在这个 Shadow DOM 中。(译注:不进不出,没有样式能进来,组件样式出不去。)
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
(and renaming) the CSS code to effectively scope the CSS to the component's view.
For details, see [Appendix 1](guide/component-styles#inspect-generated-css).
* `None` means that Angular does no view encapsulation.
`Emulated`模式(**默认值**通过预处理并改名CSS 代码来模拟 Shadow DOM 的行为,以达到把 CSS 样式局限在组件视图中的目的。
更多信息,见[附录 1](guide/component-styles#inspect-generated-css) 。(译注:只进不出,全局样式能进来,组件样式出不去)
* `None` means that Angular does no view encapsulation.
Angular adds the CSS to the global styles.
The scoping rules, isolations, and protections discussed earlier don't apply.
This is essentially the same as pasting the component's styles into the HTML.
`None`意味着 Angular 不使用视图封装。
Angular 会把 CSS 添加到全局样式中。而不会应用上前面讨论过的那些作用域规则、隔离和保护等。
从本质上来说,这跟把组件的样式直接放进 HTML 是一样的。(译注:能进能出。)
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
通过组件元数据中的`encapsulation`属性来设置组件封装模式:
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" title="src/app/quest-summary.component.ts" linenums="false">
@ -314,6 +474,9 @@ for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
which is why `Emulated` view encapsulation is the default mode and recommended
in most cases.
原生(`Native`)模式只适用于[有原生 Shadow DOM 支持的浏览器](http://caniuse.com/#feat=shadowdom)。
因此仍然受到很多限制,这就是为什么我们会把仿真 (`Emulated`) 模式作为默认选项,并建议将其用于大多数情况。
{@a inspect-generated-css}
@ -321,13 +484,19 @@ in most cases.
## Appendix 1: Inspecting the CSS generated in emulated view encapsulation
## 附录 1查看仿真 (Emulated) 模式下生成的 CSS
When using emulated view encapsulation, Angular preprocesses
all component styles so that they approximate the standard shadow CSS scoping rules.
当使用默认的仿真模式时Angular 会对组件的所有样式进行预处理,让它们模仿出标准的 Shadow CSS 作用域规则。
In the DOM of a running Angular application with emulated view
encapsulation enabled, each DOM element has some extra attributes
attached to it:
当我们查看启用了仿真模式的 Angular 应用时,我们看到每个 DOM 元素都被加上了一些额外的属性。
<code-example format="">
&lt;hero-details _nghost-pmm-5>
@ -343,15 +512,26 @@ attached to it:
There are two kinds of generated attributes:
我们看到了两种被生成的属性:
* An element that would be a shadow DOM host in native encapsulation has a
generated `_nghost` attribute. This is typically the case for component host elements.
一个元素在原生封装方式下可能是 Shadow DOM 的宿主,在这里被自动添加上一个`_nghost`属性。
这是组件宿主元素的典型情况。
* An element within a component's view has a `_ngcontent` attribute
that identifies to which host's emulated shadow DOM this element belongs.
组件视图中的每一个元素,都有一个`_ngcontent`属性,它会标记出该元素是哪个宿主的模拟 Shadow DOM。
The exact values of these attributes aren't important. They are automatically
generated and you never refer to them in application code. But they are targeted
by the generated component styles, which are in the `<head>` section of the DOM:
这些属性的具体值并不重要。它们是自动生成的,并且我们永远不会在程序代码中直接引用到它们。
但它们会作为生成的组件样式的目标,就像我们在 DOM 的`<head>`区所看到的:
<code-example format="">
[_nghost-pmm-5] {
@ -372,6 +552,9 @@ These styles are post-processed so that each selector is augmented
with `_nghost` or `_ngcontent` attribute selectors.
These extra selectors enable the scoping rules described in this page.
这些就是我们写的那些样式被处理后的结果,于是每个选择器都被增加了`_nghost`或`_ngcontent`属性选择器。
在这些附加选择器的帮助下,我们实现了本指南中所描述的这些作用域规则。
{@a relative-urls}
@ -379,8 +562,13 @@ These extra selectors enable the scoping rules described in this page.
## Appendix 2: Loading styles with relative URLs
## 附录 2使用相对 URL 加载样式
It's common practice to split a component's code, HTML, and CSS into three separate files in the same directory:
把组件的代码 (ts/js)、HTML 和 CSS 分别放到同一个目录下的三个不同文件,是一个常用的实践:
<code-example format="nocode">
quest-summary.component.ts
quest-summary.component.html
@ -394,9 +582,14 @@ You include the template and CSS files by setting the `templateUrl` and `styleUr
Because these files are co-located with the component,
it would be nice to refer to them by name without also having to specify a path back to the root of the application.
我们会通过设置元数据的`templateUrl`和`styleUrls`属性把模板和 CSS 文件包含进来。
既然这些文件都与组件(代码)文件放在一起,那么通过名字,而不是到应用程序根目录的全路径来指定它,就会是一个漂亮的方式。
You can use a relative URL by prefixing your filenames with `./`:
我们也可以通过为文件名加上`./`前缀来使用相对URL
<code-example path="component-styles/src/app/quest-summary.component.ts" title="src/app/quest-summary.component.ts">

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,83 @@
@title
Deployment
部署
@intro
Learn how to deploy your Angular app.
如何部署Angular应用。
@description
This page describes tools and techniques for deploy and optimize your Angular application.
本章会描述部署和优化Angular应用的工具与技术。
{@a toc}
## Table of contents
## 目录
* [Overview](guide/deployment#overview)
[概览](guide/deployment#overview)
* [Simplest deployment possible](guide/deployment#dev-deploy)
[最简化的部署方式](guide/deployment#dev-deploy)
* [Optimize for production](guide/deployment#optimize)
[为生产环境优化](guide/deployment#optimize)
* [Ahead-of-Time (AOT) compilation](guide/deployment#aot)
[预编译AOT](guide/deployment#aot)
* [Webpack](guide/deployment#webpack)
* [Tree shaking with _rollup_](guide/deployment#rollup)
[使用`rollup`进行摇树优化](guide/deployment#rollup)
* [Pruned libraries](guide/deployment#prune)
[库的修剪](guide/deployment#prune)
* [Measure performance first](guide/deployment#measure)
[首先,度量性能](guide/deployment#measure)
* [Angular configuration](guide/deployment#angular-configuration)
[Angular配置](guide/deployment#angular-configuration)
* [The `base` tag](guide/deployment#base-tag)
[`base`标签](guide/deployment#base-tag)
* [Enable production mode](guide/deployment#enable-prod-mode)
[启用生产模式](guide/deployment#enable-prod-mode)
* [Lazy loading](guide/deployment#lazy-loading)
[惰性加载](guide/deployment#lazy-loading)
* [Server configuration](guide/deployment#server-configuration)
[服务器配置](guide/deployment#server-configuration)
* [Routed apps must fallback to `index.html`](guide/deployment#fallback)
[带路由的应用必须以`index.html`作为后备页面](guide/deployment#fallback)
* [CORS: requesting services from a different server](guide/deployment#cors)
[CORS从其它服务器请求服务](guide/deployment#cors)
{@a overview}
@ -38,25 +85,41 @@ This page describes tools and techniques for deploy and optimize your Angular ap
## Overview
## 概览
This guide describes techniques for preparing and deploying an Angular application to a server running remotely.
The techniques progress from _easy but suboptimal_ to _more optimal and more involved_.
本章描述把Angular应用发布到远端服务器时所需的准备与部署技术。从*简单却未优化*的版本到*充分优化但涉及更多知识*的版本。
* The [simple way](guide/deployment#dev-deploy "Simplest deployment possible") is to copy the development environment to the server.
[最简单的方式](guide/deployment#dev-deploy "Simplest deployment possible")只是把文件拷贝到服务器上的部署环境
* [_Ahead of Time_ compilation (AOT)](guide/deployment#aot "AOT Compilation") is the first of
[several optimization strategies](guide/deployment#optimize).
You'll also want to read the [detailed instructions in the AOT Cookbook](cookbook/aot-compiler "AOT Cookbook").
[*预编译*(AOT)](guide/deployment#aot "AOT Compilation")是第一种[优化策略](guide/deployment#optimize)。
详情参见[烹饪宝典中的AOT章节](cookbook/aot-compiler "AOT Cookbook")。
* [Webpack](guide/deployment#webpack "Webpack Optimization") is a popular general purpose packaging tool with a rich ecosystem, including plugins for AOT.
The Angular [webpack guide](guide/webpack "Webpack: an introduction") can get you started and
_this_ page provides additional optimization advice, but you'll probably have to learn more about webpack on your own.
[Webpack](guide/deployment#webpack "Webpack Optimization")是具有完善生态系统的常用通用打包工具包括为AOT准备的插件。
你可以从[Webpack章](guide/webpack "Webpack: an introduction")开始它提供了更多优化建议但是你可能要自己去学习更多Webpack的知识。
* The [Angular configuration](guide/deployment#angular-configuration "Angular configuration") section calls attention to
specific client application changes that could improve performance.
[Angular配置](guide/deployment#angular-configuration "Angular configuration")一节专注于如何修改应用程序来提高性能。
* The [Server configuration](guide/deployment#server-configuration "Server configuration") section describes
server-side changes that may be necessary, _no matter how you deploy the application_.
[服务端配置](guide/deployment#server-configuration "Server configuration")一节描述了服务端的修改,*无论我们打算如何部署本应用*。
{@a dev-deploy}
@ -64,15 +127,23 @@ server-side changes that may be necessary, _no matter how you deploy the applica
## Simplest deployment possible
## 最简化的部署方式
The simplest way to deploy the app is to publish it to a web server
directly out of the development environment.
部署应用最简化的方式是直接把它发布到开发环境之外的Web服务器上。
It's already running locally. You'll just copy it, almost _as is_,
to a non-local server that others can reach.
它已经在本地运行过了。我们基本上只要把它原封不动的复制到别人能访问到的非本地服务器上就可以了。
1. Copy _everything_ (or [_almost_ everything](guide/deployment#node-modules "Loading npm packages from the web"))
from the local project folder to a folder on the server.
把一切文件(或[*几乎*一切文件](guide/deployment#node-modules "Loading npm packages from the web"))从本地项目目录下复制到服务器的目录下。
1. If you're serving the app out of a subfolder,
edit a version of `index.html` to set the `<base href>` appropriately.
For example, if the URL to `index.html` is `www.mysite.com/my/app/`, set the _base href_ to
@ -80,13 +151,23 @@ For example, if the URL to `index.html` is `www.mysite.com/my/app/`, set the _ba
Otherwise, leave it alone.
[More on this below](guide/deployment#base-tag).
如果准备把该应用放在子目录下,就要编辑`index.html`,并适当设置`<base href>`。
比如,如果到`index.html`的URL是`www.mysite.com/my/app/`,就把*基地址*设置为`<base href="/my/app/">`。如果是放在根路径下就不用动它。
详情参见[稍后](guide/deployment#base-tag)。
1. Configure the server to redirect requests for missing files to `index.html`.
[More on this below](guide/deployment#fallback).
把服务器上缺失的文件重定向到`index.html`,详情参见[稍后](guide/deployment#fallback)。
1. Enable production mode as [described below](guide/deployment#enable-prod-mode) (optional).
按照[稍后](guide/deployment#enable-prod-mode)的描述启用生产模式(可选)。
That's the simplest deployment you can do.
这就是最简化的部署方式。
<div class="alert is-helpful">
@ -96,6 +177,10 @@ This is _not_ a production deployment. It's not optimized and it won't be fast f
It might be good enough for sharing your progress and ideas internally with managers, teammates, and other stakeholders.
Be sure to read about [optimizing for production](guide/deployment#optimize "Optimizing for production") below.
这不是生产级部署。它没有优化过,并且对用户来说也不够快。
但是当你向经理、团队成员或其它利益相关者内部分享你的进度和想法时它是足够的。
一定要读读稍后的[为生产环境优化](guide/deployment#optimize "Optimizing for production")
</div>
@ -107,19 +192,31 @@ Be sure to read about [optimizing for production](guide/deployment#optimize "Opt
### Load npm package files from the web (SystemJS)
### 从Web上加载npm包SystemJS
The `node_modules` folder of _npm packages_ contains much more code
than is needed to actually run your app in the browser.
The `node_modules` for the Quickstart installation is typically 20,500+ files and 180+ MB.
The application itself requires a tiny fraction of that to run.
`node_modules`文件夹包含着在浏览器中运行应用时所需的更多代码。
"快速起步"项目中所需的`node_modules`通常由20,500+个文件和180+ MB的体积。
运行应用时其实只需要其中很小的一部分。
It takes a long time to upload all of that useless bulk and
users will wait unnecessarily while library files download piecemeal.
上传这些不需要的文件需要很长时间,而在库的下载期间,用户得进行不必要的等待。
Load the few files you need from the web instead.
我们可以转而从网上下载所需的这少量文件。
(1) Make a copy of `index.html` for deployment and replace all `node_module` scripts
with versions that load from the web. It might look like this.
(1) 复制一份专用于部署的`index.html`,并把所有的`node_module`脚本替换成加载网上的版本。代码如下:
<code-example path="deployment/src/index.html" region="node-module-scripts" linenums="false">
@ -130,6 +227,9 @@ with versions that load from the web. It might look like this.
(2) Replace the `systemjs.config.js` script with a script that
loads `systemjs.config.server.js`.
(2) 把`systemjs.config.js`脚本改为加载`systemjs.config.server.js`。
<code-example path="deployment/src/index.html" region="systemjs-config" linenums="false">
</code-example>
@ -140,11 +240,18 @@ loads `systemjs.config.server.js`.
This alternative version configures _SystemJS_ to load _UMD_ versions of Angular
(and other third-party packages) from the web.
(3) 把 `systemjs.config.server.js`(稍后有代码)复制到`src/`文件夹。
这个版本会从网上加载Angular的*UMD*版本(和其它第三方包)。
Modify `systemjs.config.server.js` as necessary to stay in sync with changes
you make to `systemjs.config.js`.
把对`systemjs.config.js`的修改也随时同步到`systemjs.config.server.js`文件。
Notice the `paths` key:
注意`paths`属性:
<code-example path="deployment/src/systemjs.config.server.js" region="paths" linenums="false">
@ -159,15 +266,27 @@ a site that hosts _npm packages_,
and loads them from the web directly.
There are other service providers that do the same thing.
在标准的SystemJS配置中`npm`路径指向`node_modules/`。
在服务器端的配置中,它指向<a href="https://unpkg.com/" target="_blank" title="unpkg.com">https://unpkg.com</a>(一个专门存放*npm包*的服务器),
并从网上直接加载它们。
还有另一些服务提供商做同样的事。
If you are unwilling or unable to load packages from the open web,
the inventory in `systemjs.config.server.js` identifies the files and folders that
you would copy to a library folder on the server.
Then change the config's `'npm'` path to point to that folder.
如果你不想或无法从公网上加载这些包,也可以把`systemjs.config.server.js`中所指出的这些文件或文件夹复制到服务器上的一个库目录。
然后修改配置中的`'npm'`路径指向该文件夹。
### Practice with an example
### 用一个例子实践一下
The following trivial router sample app shows these changes.
下面这个例子展示了所有的修改。
<code-tabs>
@ -205,23 +324,38 @@ The following trivial router sample app shows these changes.
Practice with this sample before attempting these techniques on your application.
在真实应用中尝试这些技术之前,先用这个例子实践一下。
1. Follow the [setup instructions](guide/setup "Angular QuickStart setup") for creating a new project
named <code>simple-deployment</code>.
遵循[设置步骤](guide/setup "Angular QuickStart setup")创建一个名叫`simple-deployment`的新项目。
1. Add the "Simple deployment" sample files shown above.
添加上述的“简单部署”范例文件。
1. Run it with `npm start` as you would any project.
像其它项目一样使用`npm start`来运行它。
1. Inspect the network traffic in the browser developer tools.
Notice that it loads all packages from the web.
You could delete the `node_modules` folder and the app would still run
(although you wouldn't be able to recompile or launch `lite-server`
until you restored it).
在浏览器的开发者工具中审查网络包。注意,它从网上加载了所有包。
我们可以删除`node_modules`文件夹,该应用仍然可以正常工作(但没办法再重新编译它或者启动`lite-server`了)。
1. Deploy the sample to the server (minus the `node_modules` folder!).
把范例工程部署到服务器上(但`node_modules`文件夹除外)
When you have that working, try the same process on your application.
掌握这些之后,就可以在你的真实项目中试用这些过程了。
{@a optimize}
@ -229,72 +363,143 @@ When you have that working, try the same process on your application.
## Optimize for production
## 为生产环境优化
Although deploying directly from the development environment works, it's far from optimal.
虽然可以直接从开发环境下部署,但是它还远远没有优化。
The client makes many small requests for individual application code and template files,
a fact you can quickly confirm by looking at the network tab in a browser's developer tools.
Each small file download can spend more time communicating with the server than tranfering data.
客户端发起了很多小的请求来取得一个个单独的应用代码和模板文件从浏览器开发工具的Network标签中就可以确认这一点。
每个小文件都会花费很多时间在与服务器建立通讯而不是传输内容上。
Development files are full of comments and whitespace for easy reading and debugging.
The browser downloads entire libraries, instead of just the parts the app needs.
The volume of code passed from server to client (the "payload")
can be significantly larger than is strictly necessary to execute the application.
开发环境下的文件有很多注释和空格,以便于阅读和调试。
浏览器会下载整个库,而不只是应用需要的那部分。
从服务器传到客户端的代码(即有效载荷)的数量会显著大于应用运行时真正需要的那部分。
The many requests and large payloads mean
the app takes longer to launch than it would if you optimized it.
Several seconds may pass (or worse) before the user can see or do anything userful.
大量请求和载荷意味着应用相对于优化过的版本会花更多时间进行启动。
当用户看到什么或做什么有用的事情之前,就已经过去了(浪费了)很多秒。
Does it matter? That depends upon business and technical factors you must evaluate for yourself.
这重要吗?取决于很多业务和技术方面的因素,我们必须自己评估它们。
If it _does_ matter, there are tools and techniques to reduce the number of requests and the size of responses.
如果重要,那么有很多工具和技术可以减少请求数和体积。
* Ahead-of-Time (AOT) Compilation: pre-compiles Angular component templates.
预编译AOT预编译Angular的组件模板。
* Bundling: concatenates modules into a single file (bundle).
打捆Bundle把这些模块串接成一个单独的捆文件bundle
* Inlining: pulls template html and css into the components.
内联把模板html和css拉到组件中。
* Minification: removes excess whitespace, comments, and optional tokens.
最小化移除不必要的空格、注释和可选令牌Token
* Uglification: rewrites code to use short, cryptic variable and function names.
混淆:使用短的、无意义的变量名和函数名来重写代码。
* Dead code elimination: removes unreferenced modules and unused code.
消除死代码:移除未引用过的模块和未使用过的代码。
* Pruned libraries: drop unused libraries and pare others down to the features you need.
修剪库:移除未使用过的库,并把其它库裁剪到只剩下你需要的那些特性。
* Performance measurement: focus on optimizations that make a measurable difference.
性能度量:集中精力做那些能产生可测量差异的优化。
Each tool does something different.
They work best in combination and are mutually reinforcing.
每个工具做的事情都不一样,但它们结合起来会相辅相成。
You can use any build system you like.
Whatever system you choose, be sure to automate it so that
building for production is a single step.
我们也可以使用任何喜欢的构建系统。
无论选择的是什么,都务必把它自动化,以便可以一步构建出产品。
{@a aot}
### Ahead-of-Time (AOT) compilation
### 预编译AOT
The Angular _Ahead-of-Time_ compiler pre-compiles application components and their templates
during the build process.
Angular的*预编译*器会在构建过程中预先编译应用的组件及其模板。
Apps compiled with AOT launch faster for several reasons.
预编译过的应用启动更快,原因如下:
* Application components execute immediately, without client-side compilation.
应用组件会立即执行,不需要客户端编译过程。
* Templates are embedded as code within their components so there is no client-side request for template files.
模板会被内嵌在组件中,因此不会再从客户端请求模板文件。
* You don't download the Angular compiler, which is pretty big on its own.
我们不用再下载Angular编译器模块它本身太大了。
* The compiler discards unused Angular directives that a tree-shaking tool can then exclude.
编译器会丢弃那些摇树优化tree-shaking工具能排除的代码。
Learn more about AOT Compilation in the [AOT Cookbook](cookbook/aot-compiler "AOT Cookbook")
which describes running the AOT compiler from the command line
and using [_rollup_](guide/deployment#rollup) for bundling, minification, uglification and tree shaking.
要了解AOT编译器的更多知识参见[烹饪宝典中的AOT一章](cookbook/aot-compiler "AOT Cookbook")
它描述了如何在命令行中执行AOT编译器并使用[_rollup_](guide/deployment#rollup)进行构建、最小化、混淆和摇树优化。
{@a webpack}
### Webpack (and AOT)
### Webpack与AOT
<a href="https://webpack.js.org/" target="_blank" title="Webpack 2">Webpack 2</a> is another
great option for inlining templates and style-sheets, for bundling, minifying, and uglifying the application.
The "[Webpack: an introduction](guide/webpack "Webpack: an introduction")" guide will get you started
using webpack with Angular.
<a href="https://webpack.js.org/" target="_blank" title="Webpack 2">Webpack 2</a>是另一个选项,它可以内联模板、样式表、打包、最小化和混淆应用。
"[Webpack简介](guide/webpack "Webpack: an introduction")"一章中将会教你如何配合Angular使用Webpack。
Consider configuring _Webpack_ with the official
<a href="https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack" target="_blank" title="Ahead-of-Time Webpack Plugin">
Angular Ahead-of-Time Webpack Plugin</a>.
@ -302,18 +507,30 @@ This plugin transpiles the TypeScript application code,
bundles lazy loaded `NgModules` separately,
and performs AOT compilation &mdash; without any changes to the source code.
考虑使用官方的<a href="https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack" target="_blank" title="Ahead-of-Time Webpack Plugin">
Angular预编译插件</a>来配置*Webpack*。
这个插件会转译TypeScript代码、独立打包延迟加载的`NgModules`而且不用对源码做任何修改就能执行AOT编译。
{@a rollup}
### Dead code elimination with _rollup_
### 使用`rollup`消除死代码
Any code that you don't call is _dead code_.
You can reduce the total size of the application substantially by removing dead code from the application and from third-party libraries.
任何永远不会调到的代码就是*死代码*。
通过移除应用和第三方库中的死代码,可以实质性减小应用的总大小。
_Tree shaking_ is a _dead code elimination_ technique that removes entire exports from JavaScript modules.
If a library exports something that the application doesn't import, a tree shaking tool removes it from the code base.
*摇树优化*是一种*消除死代码*的技术它会从JavaScript模块中移除导出。
如果一个库导出了一些东西,但是应用代码没有导入过它,摇树工具就会从代码中移除它。
Tree shaking was popularized by
<a href="http://rollupjs.org/" target="_blank" title="Rollup">Rollup</a>, a popular tool with an ecosystem of
plugins for bundling, minification, and uglification.
@ -321,40 +538,65 @@ Learn more about tree shaking and dead code elmination in
<a href="https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80#.15ih9cyvl" target="_blank" title="Tree-shaking and Dead Code Elimination">
this post</a> by rollup-creator, Rich Harris.
常用的摇树优化工具是<a href="http://rollupjs.org/" target="_blank" title="Rollup">Rollup</a>,一个带有查件的生态系统,可以完成打包、最小化和混淆。
要了解关于摇树优化和消除死代码技术的更多知识,参见<a href="https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80#.15ih9cyvl" target="_blank" title="Tree-shaking and Dead Code Elimination">这个帖子</a>它的作者就是rollup之父Rich Harris。
{@a prune}
### Pruned libraries
### 修剪库
Don't count on automation to remove all dead code.
不要指望自动移除所有死代码。
Remove libraries that you don't use, especially unnecessary scripts in `index.html`.
Consider smaller alternatives to the libraries that you do use.
手动移除不用的库,特别是`index.html`中不用的脚本。
为实际使用的那些库则努力选择更小的代用库。
Some libraries offer facilities for building a custom, skinny version with just the features you need.
Other libraries let you import features _a la carte_.
**RxJS** is a good example; import RxJS `Observable` operators individually instead of the entire library.
有些库可以构建出只带所需特性的、自定义的、带皮肤的版本。另一些库则可以让你按需导入它的特性。
**RxJS**就是一个很好的例子,我们会单独导入`Observable`的操作符operator而不是导入整个库。
{@a measure}
### Measure performance first
### 首先,度量性能
You can make better decisions about what to optimize and how when you have a clear and accurate understanding of
what's making the application slow.
The cause may not be what you think it is.
You can waste a lot of time and money optimizing something that has no tangible benefit or even makes the app slower.
You should measure the app's actual behavior when running in the environments that are important to you.
如果我们能对“是什么导致了应用变慢”的问题有一个清晰、准确的理解,那就可以对优化什么、如何优化做出更好地决策了。
真正的原因可能并不是你所想的那样。
我们可能花费大量的时间和金钱去优化一些东西,但它却无法产生可感知的效果甚至让应用变得更慢。
我们应该在那些最重要的环境中实际运行,来度量应用的实际行为。
The
<a href="https://developers.google.com/web/tools/chrome-devtools/network-performance/understanding-resource-timing" target="_blank" title="Chrome DevTools Network Performance">
Chrome DevTools Network Performance page</a> is a good place to start learning about measuring performance.
<a href="https://developers.google.com/web/tools/chrome-devtools/network-performance/understanding-resource-timing" target="_blank" title="Chrome DevTools Network Performance">
Chrome开发工具的网络性能页</a>是开始学习度量性能的好地方。
The [WebPageTest](https://www.webpagetest.org/) tool is another good choice
that can also help verify that your deployment was successful.
[WebPageTest](https://www.webpagetest.org/)工具是另一个不错的选择,它能帮你验证你的部署是否成功了。
{@a angular-configuration}
@ -362,20 +604,30 @@ that can also help verify that your deployment was successful.
## Angular configuration
## Angular配置
Angular configuration can make the difference between whether the app launches quickly or doesn't load at all.
修改Angular配置可以显示出快速启动应用和完全不加载之间的差异。
{@a base-tag}
### The `base` tag
### `base`标签
The HTML [_&lt;base href="..."/&gt;_](https://angular.io/docs/ts/latest/guide/router.html#!)
specifies a base path for resolving relative URLs to assets such as images, scripts, and style sheets.
For example, given the `<base href="/my/app/">`, the browser resolves a URL such as `some/place/foo.jpg`
into a server request for `my/app/some/place/foo.jpg`.
During navigation, the Angular router uses the _base href_ as the base path to component, template, and module files.
HTML中的[_&lt;base href="..."/&gt;_](https://angular.io/docs/ts/latest/guide/router.html#!)用于指定一个解析相对路径的基地址,如图片、脚本和样式表。
比如,指定`<base href="/my/app/">`时,浏览器就会把`some/place/foo.jpg`这样的URL解析成到`my/app/some/place/foo.jpg`的服务端请求。
在浏览期间Angular路由器会使用*base href*作为组件、模板和模块文件的基地址。
<div class="l-sub-section">
@ -383,6 +635,9 @@ During navigation, the Angular router uses the _base href_ as the base path to c
See also the [*APP_BASE_HREF*](api/common/index/APP_BASE_HREF-let "API: APP_BASE_HREF") alternative.
参见另一种备选方案[*APP_BASE_HREF*](api/common/index/APP_BASE_HREF-let "API: APP_BASE_HREF")。
</div>
@ -390,33 +645,51 @@ See also the [*APP_BASE_HREF*](api/common/index/APP_BASE_HREF-let "API: APP_BASE
In development, you typically start the server in the folder that holds `index.html`.
That's the root folder and you'd add `<base href="/">` near the top of `index.html` because `/` is the root of the app.
在开发期间,我们通常会在`index.html`所在的目录中启动服务器。这个目录就是根目录,因为`/`就是本应用的根,所以我们要在`index.html`的顶部添加`<base href="/">`。
But on the shared or production server, you might serve the app from a subfolder.
For example, when the URL to load the app is something like `http://www.mysite.com/my/app/`,
the subfolder is `my/app/` and you should add `<base href="/my/app/">` to the server version of the `index.html`.
但是在共享服务器或生产服务器上,我们可能得从子目录下启动服务器。
比如当加载本应用的URL是`http://www.mysite.com/my/app/`时,子目录就是`my/app/`,而我们就要在服务器版的`index.html`中添加`<base href="/my/app/">`。
When the `base` tag is misconfigured, the app fails to load and the browser console displays `404 - Not Found` errors
for the missing files. Look at where it _tried_ to find those files and adjust the base tag appropriately.
当`base`标签没有正确配置时,该应用会加载失败,并且浏览器的控制台会对这些缺失的文件显示`404 - Not Found`错误。
看看它在尝试从哪里查找那些文件并据此调整base标签。
{@a enable-prod-mode}
### Enable production mode
### 启用生产模式
Angular apps run in development mode by default, as you can see by the following message on the browser
console:
Angular应用默认运行在开发模式下正如在浏览器控制台中看到的如下信息
<code-example format="nocode">
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Angular正运行在开发模式下。调用enableProdMode()来启用生产模式)
</code-example>
Switching to production mode can make it run faster by disabling development specific checks such as the dual change detection cycles.
切换到生产模式可以通过禁用开发环境下特有的检查(比如双重变更检测周期)来让应用运行得更快。
To enable [production mode](api/core/index/enableProdMode-function) when running remotely, add the following code to the `main.ts`.
要在远程运行时启用[生产模式](api/core/index/enableProdMode-function),请把下列代码添加到`main.ts`中。
<code-example path="deployment/src/main.ts" region="enableProdMode" title="src/main.ts (enableProdMode)" linenums="false">
@ -429,31 +702,52 @@ To enable [production mode](api/core/index/enableProdMode-function) when running
### Lazy loading
### 惰性加载
You can dramatically reduce launch time by only loading the application modules that
absolutely must be present when the app starts.
通过只加载应用启动时必须展示的那些应用模块,我们可以显著缩减启动时间。
Configure the Angular Router to defer loading of all other modules (and their associated code), either by
[waiting until the app has launched](guide/router#preloading "Preloading")
or by [_lazy loading_](guide/router#asynchronous-routing "Lazy loading")
them on demand.
配置Angular路由器可以延迟加载所有其它模块以及与它们相关的代码无论是[等应用启动](guide/router#preloading "Preloading")
还是在需要时才[惰性加载](guide/router#asynchronous-routing "Lazy loading")。
#### Don't eagerly import something from a lazy loaded module
#### 不要立即导入惰性加载模块中的任何东西
It's a common mistake.
You've arranged to lazy load a module.
But you unintentionally import it, with a JavaScript `import` statement,
in a file that's eagerly loaded when the app starts, a file such as the root `AppModule`.
If you do that, the module will be loaded immediately.
这是一种常犯的错误。
我们本打算惰性加载一个模块,但可能无意中在根模块`AppModule`文件中使用一个JavaScript的`import`语句导入了它。
这样一来,该模块就被立即加载了。
The bundling configuration must take lazy loading into consideration.
Because lazy loaded modules aren't imported in JavaScript (as just noted), bundlers exclude them by default.
Bundlers don't know about the router configuration and won't create separate bundles for lazy loaded modules.
You have to create these bundles manually.
关于打包bundle方式的配置必须考虑到惰性加载问题。
因为惰性加载模块不能在JavaScript中导入就像刚才说明的打包器应该默认排除它们。
打包器不知道路由器的配置,并且不会为延迟加载模块创建单独的包。
我们不得不手动创建这些包。
The
[Angular Ahead-of-Time Webpack Plugin](https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack)
automatically recognizes lazy loaded `NgModules` and creates separate bundles for them.
[Angular预编译插件](https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack)会自动识别惰性加载的`NgModules`,并为它们创建单独的包。
{@a server-configuration}
@ -462,21 +756,32 @@ automatically recognizes lazy loaded `NgModules` and creates separate bundles fo
## Server configuration
## 服务端配置
This section covers changes you may have make to the server or to files deployed to the server.
这一节涵盖了我们对服务器或准备部署到服务器的文件要做的那些修改。
{@a fallback}
### Routed apps must fallback to `index.html`
### 带路有的应用必须以`index.html`作为后备页面
Angular apps are perfect candidates for serving with a simple static HTML server.
You don't need a server-side engine to dynamically compose application pages because
Angular does that on the client-side.
Angular应用很适合用简单的静态HTML服务器提供服务。
我们不需要服务端引擎来动态合成应用页面因为Angular会在客户端完成这件事。
If the app uses the Angular router, you must configure the server
to return the application's host page (`index.html`) when asked for a file that it does not have.
如果该应用使用Angular路由器我们就必须配置服务器让它对不存在的文件返回应用的宿主页(`index.html`)。
{@a deep-link}
@ -486,32 +791,57 @@ A _deep link_ is a URL that specifies a path to a component inside the app.
For example, `http://www.mysite.com/heroes/42` is a _deep link_ to the hero detail page
that displays the hero with `id: 42`.
带路由的应用应该支持“深链接”。
所谓*深链接*就是指一个URL它用于指定到应用内某个组件的路径。
比如,`http://www.mysite.com/heroes/42`就是一个到英雄详情页面的*深链接*,用于显示`id: 42`的英雄。
There is no issue when the user navigates to that URL from within a running client.
The Angular router interprets the URL and routes to that page and hero.
当用户从运行中的客户端应用导航到这个URL时这没问题。
Angular路由器会拦截这个URL并且把它路由到正确的页面。
But clicking a link in an email, entering it in the browser address bar,
or merely refreshing the browser while on the hero detail page &mdash;
all of these actions are handled by the browser itself, _outside_ the running application.
The browser makes a direct request to the server for that URL, bypassing the router.
但是,当从邮件中点击链接或在浏览器地址栏中输入它或仅仅在英雄详情页刷新下浏览器时,所有这些操作都是由浏览器本身处理的,在应用的控制范围之外。
浏览器会直接向服务器请求那个URL路由器没机会插手。
A static server routinely returns `index.html` when it receives a request for `http://www.mysite.com/`.
But it rejects `http://www.mysite.com/heroes/42` and returns a `404 - Not Found` error *unless* it is
configured to return `index.html` instead.
静态服务器会在收到对`http://www.mysite.com/`的请求时返回`index.html`,但是会拒绝对`http://www.mysite.com/heroes/42`的请求,
并返回一个`404 - Not Found`错误,除非,我们把它配置成转而返回`index.html`。
#### Fallback configuration examples
#### 后备页面配置范例
There is no single configuration that works for every server.
The following sections describe configurations for some of the most popular servers.
The list is by no means exhaustive, but should provide you with a good starting point.
没有一种配置可以适用于所有服务器。
后面这些部分会描述对常见服务器的配置方式。
这个列表虽然不够详尽,但可以为你提供一个良好的起点。
#### Development servers
#### 开发服务器
* [Lite-Server](https://github.com/johnpapa/lite-server): the default dev server installed with the
[Quickstart repo](https://github.com/angular/quickstart) is pre-configured to fallback to `index.html`.
[Lite-Server](https://github.com/johnpapa/lite-server)是["快速起步"仓库](https://github.com/angular/quickstart)中安装的默认开发服务器,它被预先配置为回退到`index.html`。
* [Webpack-Dev-Server](https://github.com/webpack/webpack-dev-server): setup the
`historyApiFallback` entry in the dev server options as follows:
[Webpack-Dev-Server](https://github.com/webpack/webpack-dev-server)在开发服务器的配置中设置了`historyApiFallback`,代码如下:
<code-example>
historyApiFallback: {
@ -525,11 +855,16 @@ The list is by no means exhaustive, but should provide you with a good starting
#### Production servers
#### 生产服务器
* [Apache](https://httpd.apache.org/): add a
[rewrite rule](http://httpd.apache.org/docs/current/mod/mod_rewrite.html)
to the `.htaccess` file as show
[here](https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/):
[Apache](https://httpd.apache.org/):在`.htaccess`文件中添加一个[重写规则](http://httpd.apache.org/docs/current/mod/mod_rewrite.html)
代码如下([出处](https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/)
<code-example format=".">
RewriteEngine On
@ -549,6 +884,8 @@ to the `.htaccess` file as show
[Front Controller Pattern Web Apps](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#front-controller-pattern-web-apps),
modified to serve `index.html`:
[NGinx](http://nginx.org/):使用`try_files`指向`index.html`,详细描述见[Web应用的前端控制器模式](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#front-controller-pattern-web-apps)。
<code-example format=".">
try_files $uri $uri/ /index.html;
@ -560,6 +897,9 @@ modified to serve `index.html`:
* [IIS](https://www.iis.net/): add a rewrite rule to `web.config`, similar to the one shown
[here](http://stackoverflow.com/a/26152011/2116927):
[IIS](https://www.iis.net/):往`web.config`中添加一条重写规则,类似于[这里](http://stackoverflow.com/a/26152011/2116927)
<code-example format='.'>
&lt;system.webServer&gt;
&lt;rewrite&gt;
@ -590,9 +930,16 @@ It's also a good idea to
and to
[create a `.nojekyll` file](https://www.bennadel.com/blog/3181-including-node-modules-and-vendors-folders-in-your-github-pages-site.htm)
[GitHub页面服务](https://pages.github.com/):我们没办法[直接配置](https://github.com/isaacs/github/issues/408) Github的页面服务但可以添加一个404页只要把`index.html`复制到`404.html`就可以了。
它仍然会给出一个404响应但是浏览器将会正确处理该页并正常加载该应用。
使用[在主分支的`docs/`下启动服务](https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch)
并[创建一个`.nojekyll`文件](https://www.bennadel.com/blog/3181-including-node-modules-and-vendors-folders-in-your-github-pages-site.htm)也是一个好办法。
* [Firebase hosting](https://firebase.google.com/docs/hosting/): add a
[rewrite rule](https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites).
[Firebase主机服务](https://firebase.google.com/docs/hosting/):添加一条[重写规则](https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites)。
<code-example format=".">
"rewrites": [ {
@ -610,22 +957,36 @@ and to
### Requesting services from a different server (CORS)
### 请求来自另一个服务器的服务CORS
Angular developers may encounter a
<a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing" target="_blank" title="Cross-origin resource sharing">
<i>cross-origin resource sharing</i></a> error when making a service request (typically a data service request).
to a server other than the application's own host server.
Browsers forbid such requests unless the server permits them explicitly.
Angular开发者在向与该应用的宿主服务器不同域的服务器发起请求时可能会遇到一种<a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing" target="_blank" title="Cross-origin resource sharing"><i>跨域资源共享CORS</i></a>错误。
浏览器会阻止该请求,除非得到那台服务器的明确许可。
There isn't anything the client application can do about these errors.
The server must be configured to accept the application's requests.
Read about how to enable CORS for specific servers at
<a href="http://enable-cors.org/server.html" target="_blank" title="Enabling CORS server">enable-cors.org</a>.
客户端应用对这种错误无能为力。
服务器必须配置成可以接受来自该应用的请求。
要了解如何对特定的服务器开启CORS参见<a href="http://enable-cors.org/server.html" target="_blank" title="Enabling CORS server">enable-cors.org</a>
{@a next-steps}
## Next steps
If you want to go beyond the [simple _copy-deploy_](guide/deployment#dev-deploy "Simplest deployment possible") approach,
read the [AOT Cookbook](cookbook/aot-compiler "AOT Cookbook") next.
## 下一步
If you want to go beyond the [simple _copy-deploy_](guide/deployment#dev-deploy "Simplest deployment possible") approach,
read the [AOT Cookbook](cookbook/aot-compiler "AOT Cookbook") next.
如果我们准备超越[简单*复制*部署](guide/deployment#dev-deploy "Simplest deployment possible")的方式,请参阅[烹饪宝典中的AOT部分](cookbook/aot-compiler "AOT Cookbook")。

View File

@ -1,8 +1,8 @@
@title
Displaying Data
显示数据
@intro
Property binding helps show app data in the UI.
属性绑定机制把数据显示到用户界面上。
@description
@ -10,12 +10,19 @@ Property binding helps show app data in the UI.
You can display data by binding controls in an HTML template to properties of an Angular component.
在 Angular 中最典型的数据显示方式,就是把 HTML 模板中的控件绑定到 Angular 组件的属性。
In this page, you'll create a component with a list of heroes.
You'll display the list of hero names and
conditionally show a message below the list.
本章中,你将创建一个英雄列表组件。
你将显示英雄名字的列表,并根据条件在列表下方显示一条消息。
The final UI looks like this:
最终的用户界面是这样的:
<figure class='image-display'>
<img src="assets/images/devguide/displaying-data/final.png" alt="Final UI"></img>
@ -25,9 +32,19 @@ The final UI looks like this:
# Contents
* [Showing component properties with interpolation](guide/displaying-data#interpolation).
* [Showing an array property with NgFor](guide/displaying-data#ngFor).
* [Conditional display with NgIf](guide/displaying-data#ngIf).
# 目录
* [Showing component properties with interpolation](guide/displaying-data#interpolation)
[通过插值表达式显示组件的属性](guide/displaying-data#interpolation)
* [Showing an array property with NgFor](guide/displaying-data#ngFor)
[通过 NgFor 显示数组型属性](guide/displaying-data#ngFor)
* [Conditional display with NgIf](guide/displaying-data#ngIf)
[通过 NgIf 实现按条件显示](guide/displaying-data#ngIf)
<div class="l-sub-section">
@ -37,6 +54,8 @@ The final UI looks like this:
The <live-example></live-example> demonstrates all of the syntax and code
snippets described in this page.
这个<live-example></live-example>演示了本章中描述的所有语法和代码片段。
</div>
@ -44,18 +63,30 @@ snippets described in this page.
## Showing component properties with interpolation
## 使用插值表达式显示组件属性
The easiest way to display a component property
is to bind the property name through interpolation.
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
要显示组件的属性,最简单的方式就是通过插值表达式 (interpolation) 来绑定属性名。
要使用插值表达式,就把属性名包裹在双花括号里放进视图模板,如`{{myHero}}`。
Follow the [setup](guide/setup) instructions for creating a new project
named <code>displaying-data</code>.
按照[开发环境](guide/setup)的说明,创建一个新项目,名为<code>displaying-data</code>
Then modify the <code>app.component.ts</code> file by
changing the template and the body of the component.
然后,到`app.component.ts`文件中修改组件的模板和代码。
When you're done, it should look like this:
修改完之后,它应该是这样的:
<code-example path="displaying-data/src/app/app.component.1.ts" title="src/app/app.component.ts">
@ -65,9 +96,13 @@ When you're done, it should look like this:
You added two properties to the formerly empty component: `title` and `myHero`.
再把两个属性`title`和`myHero`添加到之前空白的组件中。
The revised template displays the two component properties using double curly brace
interpolation:
修改完的模板会使用双花括号形式的插值表达式来显示这两个模板属性:
<code-example path="displaying-data/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
@ -84,6 +119,10 @@ The backtick (<code>\`</code>)&mdash;which is *not* the same character as a sing
quote (`'`)&mdash;allows you to compose a string over several lines, which makes the
HTML more readable.
模板是包在 ECMAScript 2015 反引号 (<code>\`</code>) 中的一个多行字符串。
反引号 (<code>\`</code>) &mdash; 注意,不是单引号 (') &mdash; 允许把一个字符串写在多行上,
使 HTML 模板更容易阅读。
</div>
@ -93,6 +132,8 @@ Angular automatically pulls the value of the `title` and `myHero` properties fro
inserts those values into the browser. Angular updates the display
when these properties change.
Angular 自动从组件中提取`title`和`myHero`属性的值并且把这些值插入浏览器中。当这些属性发生变化时Angular 就会自动刷新显示。
<div class="l-sub-section">
@ -101,6 +142,8 @@ when these properties change.
More precisely, the redisplay occurs after some kind of asynchronous event related to
the view, such as a keystroke, a timer completion, or a response to an HTTP request.
严格来说,“重新显示”是在某些与视图有关的异步事件之后发生的,例如,按键、定时器完成或对 HTTP 请求的响应。
</div>
@ -109,9 +152,14 @@ the view, such as a keystroke, a timer completion, or a response to an HTTP requ
Notice that you don't call **new** to create an instance of the `AppComponent` class.
Angular is creating an instance for you. How?
注意,我们没有调用 **new** 来创建`AppComponent`类的实例,是 Angular 替我们创建了它。那么它是如何创建的呢?
The CSS `selector` in the `@Component` decorator specifies an element named `<my-app>`.
That element is a placeholder in the body of your `index.html` file:
注意`@Component`装饰器中指定的 CSS 选择器`selector`,它指定了一个叫`my-app`的元素。
该元素是`index.html`的`body`里的占位符。
<code-example path="displaying-data/src/index.html" linenums="false" title="src/index.html (body)" region="body">
@ -123,8 +171,14 @@ When you bootstrap with the `AppComponent` class (in <code>main.ts</code>), Angu
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
inside the `<my-app>` tag.
当我们通过`main.ts`中的`AppComponent`类启动时Angular 在`index.html`中查找一个`<my-app>`元素,
然后实例化一个`AppComponent`,并将其渲染到`<my-app>`标签中。
Now run the app. It should display the title and hero name:
运行应用。它应该显示出标题和英雄名:
<figure class='image-display'>
<img src="assets/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero"></img>
</figure>
@ -133,26 +187,44 @@ Now run the app. It should display the title and hero name:
The next few sections review some of the coding choices in the app.
回顾一下前面所做的决定,看看还有哪些其它选择。
## Template inline or template file?
## 内联 (inline) 模板还是模板文件?
You can store your component's template in one of two places.
You can define it *inline* using the `template` property, or you can define
the template in a separate HTML file and link to it in
the component metadata using the `@Component` decorator's `templateUrl` property.
你可以在两种地方存放组件模板。
你可以使用`template`属性把它定义为*内联*的,或者把模板定义在一个独立的 HTML 文件中,
再通过`@Component`装饰器中的`templateUrl`属性,
在组件元数据中把它链接到组件。
The choice between inline and separate HTML is a matter of taste,
circumstances, and organization policy.
Here the app uses inline HTML because the template is small and the demo
is simpler without the additional HTML file.
到底选择内联 HTML 还是独立 HTML 取决于个人喜好、具体状况和组织级策略。
上面的应用选择内联 HTML ,是因为模板很小,而且没有额外的 HTML 文件显得这个演示简单些。
In either style, the template data bindings have the same access to the component's properties.
无论用哪种风格,模板数据绑定在访问组件属性方面都是完全一样的。
## Constructor or variable initialization?
## 使用构造函数还是变量初始化?
Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor:
虽然这个例子使用了变量赋值的方式初始化组件,你还可以使用构造函数来声明和初始化属性。
<code-example path="displaying-data/src/app/app-ctor.component.ts" linenums="false" title="src/app/app-ctor.component.ts (class)" region="class">
@ -162,12 +234,18 @@ Although this example uses variable assignment to initialize the components, you
This app uses more terse "variable assignment" style simply for brevity.
为了让本应用更加简短,它采用了更简单的“变量赋值”风格。
## Showing an array property with ***ngFor**
## 使用***ngFor***显示数组属性
To display a list of heroes, begin by adding an array of hero names to the component and redefine `myHero` to be the first name in the array.
要显示一个英雄列表,先向组件中添加一个英雄名字数组,然后把`myHero`重定义为数组中的第一个名字。
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (class)" region="class">
@ -178,6 +256,8 @@ To display a list of heroes, begin by adding an array of hero names to the compo
Now use the Angular `ngFor` directive in the template to display
each item in the `heroes` list.
接着,在模板中使用 Angular 的`ngFor`指令来显示`heroes`列表中的每一项。
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
@ -189,6 +269,9 @@ This UI uses the HTML unordered list with `<ul>` and `<li>` tags. The `*ngFor`
in the `<li>` element is the Angular "repeater" directive.
It marks that `<li>` element (and its children) as the "repeater template":
这个界面使用了由`<ul>`和`<li>`标签组成的无序列表。`<li>`元素里的`*ngFor`是 Angular 的“迭代”指令。
它将`<li>`元素及其子级标记为“迭代模板”:
<code-example path="displaying-data/src/app/app.component.2.ts" linenums="false" title="src/app/app.component.ts (li)" region="li">
@ -203,6 +286,9 @@ It marks that `<li>` element (and its children) as the "repeater template":
Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax.
For more information, see the [Template Syntax](guide/template-syntax#ngFor) page.
不要忘记`*ngFor`中的前导星号 (\*)。它是语法中不可或缺的一部分。
更多信息,见[模板语法](guide/template-syntax#ngFor)。
</div>
@ -213,10 +299,17 @@ it is an example of a template input variable. Read
more about template input variables in the [microsyntax](guide/template-syntax#microsyntax) section of
the [Template Syntax](guide/template-syntax) page.
注意看`ngFor`双引号表达式中的`hero`,它是一个模板输入变量。
更多模板输入变量的信息,见[模板语法](guide/template-syntax)中的
[微语法 (microsyntax)](guide/template-syntax#microsyntax)。
Angular duplicates the `<li>` for each item in the list, setting the `hero` variable
to the item (the hero) in the current iteration. Angular uses that variable as the
context for the interpolation in the double curly braces.
Angular 为列表中的每个条目复制一个`<li>`元素,在每个迭代中,把`hero`变量设置为当前条目(英雄)。
Angular 把`hero`变量作为双花括号插值表达式的上下文。
<div class="l-sub-section">
@ -225,15 +318,21 @@ context for the interpolation in the double curly braces.
In this case, `ngFor` is displaying an array, but `ngFor` can
repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) object.
本例中,`ngFor`用于显示一个“数组”,
但`ngFor`可以为任何[可迭代的 (iterable) ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)对象重复渲染条目。
</div>
Now the heroes appear in an unordered list.
现在,英雄们出现在了一个无序列表中。
<figure class='image-display'>
<img src="assets/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor"></img>
<img src="assets/images/devguide/displaying-data/hero-names-list.png" alt="ngfor之后"></img>
</figure>
@ -241,17 +340,28 @@ Now the heroes appear in an unordered list.
## Creating a class for the data
## 为数据创建一个类
The app's code defines the data directly inside the component, which isn't best practice.
In a simple demo, however, it's fine.
应用代码直接在组件内部直接定义了数据。
作为演示还可以,但它显然不是最佳实践。
At the moment, the binding is to an array of strings.
In real applications, most bindings are to more specialized objects.
现在使用的是到了一个字符串数组的绑定。在真实的应用中,大多是到一个对象数组的绑定。
To convert this binding to use specialized objects, turn the array
of hero names into an array of `Hero` objects. For that you'll need a `Hero` class.
要将此绑定转换成使用对象,需要把这个英雄名字数组变成`Hero`对象数组。但首先得有一个`Hero`类。
Create a new file in the `app` folder called `hero.ts` with the following code:
在`app`目录下创建一个名叫`hero.ts`的新文件,内容如下:
<code-example path="displaying-data/src/app/hero.ts" linenums="false" title="src/app/hero.ts (excerpt)">
@ -261,11 +371,17 @@ Create a new file in the `app` folder called `hero.ts` with the following code:
You've defined a class with a constructor and two properties: `id` and `name`.
你定义了一个类,具有一个构造函数和两个属性:`id`和`name`。
It might not look like the class has properties, but it does.
The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
它可能看上去不像是有属性的类,但它确实有,利用的是 TypeScript 提供的简写形式 —— 用构造函数的参数直接定义属性。
Consider the first parameter:
来看第一个参数:
<code-example path="displaying-data/src/app/hero.ts" linenums="false" title="src/app/hero.ts (id)" region="id">
@ -275,17 +391,31 @@ Consider the first parameter:
That brief syntax does a lot:
* Declares a constructor parameter and its type.
* Declares a public property of the same name.
* Initializes that property with the corresponding argument when creating an instance of the class.
这个简写语法做了很多:
* Declares a constructor parameter and its type
声明了一个构造函数参数及其类型
* Declares a public property of the same name
声明了一个同名的公共属性
* Initializes that property with the corresponding argument when we "new" an instance of the class
当我们`new`出该类的一个实例时,把该属性初始化为相应的参数值
## Using the Hero class
## 使用 Hero 类
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ array
of `Hero` objects:
导入了`Hero`类之后,组件的`heroes`属性就可以返回一个*类型化的*`Hero`对象数组了。
<code-example path="displaying-data/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (heroes)" region="heroes">
@ -297,6 +427,10 @@ Next, update the template.
At the moment it displays the hero's `id` and `name`.
Fix that to display only the hero's `name` property.
接着,更新一下模板。
现在它显示的是英雄的`id`和`name`。
要修复它,只显示英雄的`name`属性就行了。
<code-example path="displaying-data/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
@ -304,19 +438,30 @@ Fix that to display only the hero's `name` property.
The display looks the same, but the code is clearer.
Our display looks the same, but now we know much better what a hero really is.
从显示上看还是一样,但现在我们知道了更多英雄信息。
## Conditional display with NgIf
## 通过 NgIf 进行条件显示
Sometimes an app needs to display a view or a portion of a view only under specific circumstances.
有时,应用需要只在特定情况下显示视图或视图的一部分。
Let's change the example to display a message if there are more than three heroes.
让我们来修改这个例子,如果多于三位英雄,显示一条消息。
The Angular `ngIf` directive inserts or removes an element based on a _truthy/falsy_ condition.
To see it in action, add the following paragraph at the bottom of the template:
Angular 的`ngIf`指令会根据一个布尔条件来显示或移除一个元素。
来看看实际效果,把下列语句加到模板的底部:
<code-example path="displaying-data/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts (message)" region="message">
@ -331,6 +476,9 @@ To see it in action, add the following paragraph at the bottom of the template:
Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax.
Read more about `ngIf` and `*` in the [ngIf section](guide/template-syntax#ngIf) of the [Template Syntax](guide/template-syntax) page.
不要忘了`*ngIf`中的前导星号 (\*)。它是本语法中不可或缺的一部分。
更多`ngIf`和`* `的内容,见[模板语法](guide/template-syntax)中的[ngIf](guide/template-syntax#ngIf)。
</div>
@ -344,6 +492,10 @@ paragraph, so no message appears. For more information,
see the [template expressions](guide/template-syntax#template-expressions) section of the
[Template Syntax](guide/template-syntax) page.
双引号中的模板表达式`*ngIf="heros.length > 3"`,外观和行为很象 TypeScript 。
当组件中的英雄列表有三个以上的条目时Angular 把这个段落添加到 DOM 中,于是消息显示了出来。
更多信息,见[模板语法](guide/template-syntax)中的[模板表达式](guide/template-syntax#template-expressions)。
<div class="alert is-helpful">
@ -352,6 +504,9 @@ see the [template expressions](guide/template-syntax#template-expressions) secti
Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM. That improves performance, especially in larger projects when conditionally including or excluding
big chunks of HTML with many data bindings.
Angular 并不是在显示和隐藏这条消息,它是在从 DOM 中添加和移除这个段落元素。
这会提高性能,特别是在一些大的项目中有条件地包含或排除一大堆带着很多数据绑定的 HTML 时。
</div>
@ -361,18 +516,40 @@ Try it out. Because the array has four items, the message should appear.
Go back into <code>app.component.ts"</code> and delete or comment out one of the elements from the hero array.
The browser should refresh automatically and the message should disappear.
试一下。因为这个数组中有四个条目,所以消息应该显示出来。
回到`app.component.ts`,从英雄数组中删除或注释掉一个元素。
浏览器应该自动刷新,消息应该会消失。
## Summary
## 小结
Now you know how to use:
现在你知道了如何使用:
* **Interpolation** with double curly braces to display a component property.
带有双花括号的**插值表达式 (interpolation) **来显示一个组件属性。
* **ngFor** to display an array of items.
**ngFor** 显示数组。
* A TypeScript class to shape the **model data** for your component and display properties of that model.
用一个 TypeScript 类来为我们的组件描述**模型数据**并显示模型的属性。
* **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
**ngIf** 根据一个布尔表达式有条件地显示一段 HTML。
Here's the final code:
下面是最终的代码:
<code-tabs>

View File

@ -1,61 +1,105 @@
@title
Dynamic Component Loader
动态组件加载器
@intro
Load components dynamically.
如何动态加载组件
@description
Component templates are not always fixed. An application may need to load new components at runtime.
组件的模板不会永远是固定的。应用可能会需要在运行期间加载一些新的组件。
This cookbook shows you how to use `ComponentFactoryResolver` to add components dynamically.
这本烹饪书为你展示如何使用`ComponentFactoryResolver`来动态添加组件。
{@a toc}
# Contents
# 目录
* [Dynamic component loading](guide/dynamic-component-loader#dynamic-loading)
[动态组件加载](guide/dynamic-component-loader#dynamic-loading)
* [The directive](guide/dynamic-component-loader#directive)
[指令](guide/dynamic-component-loader#directive)
* [Loading components](guide/dynamic-component-loader#loading-components)
[加载组件](guide/dynamic-component-loader#loading-components)
* [Resolving Components](guide/dynamic-component-loader#resolving-components)
[解析组件](guide/dynamic-component-loader#resolving-components)
* [Selector References](guide/dynamic-component-loader#selector-references)
[通过选择器引用](guide/dynamic-component-loader#selector-references)
* [A common _AdComponent_ interface](guide/dynamic-component-loader#common-interface)
[通用的`AdComponent`接口](guide/dynamic-component-loader#common-interface)
* [Final ad banner](guide/dynamic-component-loader#final-ad-banner)
[最终的广告Banner](guide/dynamic-component-loader#final-ad-banner)
See the <live-example name="cb-dynamic-component-loader"></live-example>
of the code in this cookbook.
<live-example name="cb-dynamic-component-loader"></live-example>查看本烹饪书的源码。
{@a dynamic-loading}
## Dynamic component loading
## 动态组件加载
The following example shows how to build a dynamic ad banner.
下面的例子展示了如何构建动态广告条。
The hero agency is planning an ad campaign with several different
ads cycling through the banner. New ad components are added
frequently by several different teams. This makes it impractical
to use a template with a static component structure.
英雄管理局正在计划一个广告活动,要在广告条中显示一系列不同的广告。几个不同的小组可能会频繁加入新的广告组件。
再用只支持静态组件结构的模板显然是不现实的。
Instead, you need a way to load a new component without a fixed
reference to the component in the ad banner's template.
我们需要一种新的组件加载方式,它不需要在广告条组件的模板中引用固定的组件。
Angular comes with its own API for loading components dynamically.
Angular 自带的API就能支持动态加载组件。
{@a directive}
## The directive
## 指令
Before you can add components you have to define an anchor point
to tell Angular where to insert components.
在添加组件之前先要定义一个锚点来告诉Angular要把组件插入到什么地方。
The ad banner uses a helper directive called `AdDirective` to
mark valid insertion points in the template.
广告条使用一个名叫`AdDirective`的辅助指令来在模板中标记出有效的插入点。
<code-example path="cb-dynamic-component-loader/src/app/ad.directive.ts" title="src/app/ad.directive.ts" linenums="false">
@ -66,23 +110,36 @@ mark valid insertion points in the template.
`AdDirective` injects `ViewContainerRef` to gain access to the view
container of the element that will host the dynamically added component.
`AdDirective`注入了`ViewContainerRef`来获取对容器视图的访问权,这个容器就是那些动态加入的组件的宿主。
In the `@Directive` decorator, notice the selector name, `ad-host`;
that's what you use to apply the directive to the element.
The next section shows you how.
在`@Directive`装饰器中,要注意选择器的名称:`ad-host`,它就是我们将应用到元素上的指令。下一节我们会展示如何做。
{@a loading-components}
## Loading components
## 加载组件
Most of the ad banner implementation is in `ad-banner.component.ts`.
To keep things simple in this example, the HTML is in the `@Component`
decorator's `template` property as a template string.
广告条的大部分实现代码都在`ad-banner.component.ts`中。
为了让这个例子简单点我们把HTML直接放在了`@Component`装饰器的`template`属性中。
The `<ng-template>` element is where you apply the directive you just made.
To apply the `AdDirective`, recall the selector from `ad.directive.ts`,
`ad-host`. Apply that to `<ng-template>` without the square brackets. Now Angular knows
where to dynamically load components.
`<ng-template>`元素就是刚才制作的指令将应用到的地方。
要应用`AdDirective`,回忆一下来自`ad.directive.ts`的选择器`ad-host`。把它应用到`<ng-template>`(不用带方括号)。
这下Angular就知道该把组件动态加载到哪里了。
<code-example path="cb-dynamic-component-loader/src/app/ad-banner.component.ts" region="ad-host" title="src/app/ad-banner.component.ts (template)" linenums="false">
@ -93,25 +150,39 @@ where to dynamically load components.
The `<ng-template>` element is a good choice for dynamic components
because it doesn't render any additional output.
`<ng-template>`元素是动态加载组件的最佳选择,因为它不会渲染任何额外的输出。
{@a resolving-components}
### Resolving components
### 解析组件
Take a closer look at the methods in `ad-banner.component.ts`.
深入看看`ad-banner.component.ts`中的方法。
`AdBannerComponent` takes an array of `AdItem` objects as input,
which ultimately comes from `AdService`. `AdItem` objects specify
the type of component to load and any data to bind to the
component.`AdService` returns the actual ads making up the ad campaign.
`AdBannerComponent`接收一个`AdItem`对象的数组作为输入,它最终来自`AdService`。
`AdItem`对象指定要加载的组件类,以及绑定到该组件上的任意数据。
`AdService`可以返回广告活动中的那些广告。
Passing an array of components to `AdBannerComponent` allows for a
dynamic list of ads without static elements in the template.
给`AdBannerComponent`传入一个组件数组可以让我们在模板中放入一个广告的动态列表,而不用写死在模板中。
With its `getAds()` method, `AdBannerComponent` cycles through the array of `AdItems`
and loads a new component every 3 seconds by calling `loadComponent()`.
通过`getAds()`方法,`AdBannerComponent`可以循环遍历`AdItems`的数组,并且每三秒调用一次`loadComponent()`来加载新组件。
<code-example path="cb-dynamic-component-loader/src/app/ad-banner.component.ts" region="class" title="src/app/ad-banner.component.ts (excerpt)" linenums="false">
@ -122,6 +193,9 @@ and loads a new component every 3 seconds by calling `loadComponent()`.
The `loadComponent()` method is doing a lot of the heavy lifting here.
Take it step by step. First, it picks an ad.
这里的`loadComponent()`方法很重要。
我们来一步步看看。首先,它选取了一个广告。
<div class="l-sub-section">
@ -129,13 +203,20 @@ Take it step by step. First, it picks an ad.
**How _loadComponent()_ chooses an ad**
**`loadComponent()`如何选择广告**
The `loadComponent()` method chooses an ad using some math.
`loadComponent()`方法使用某种算法选择了一个广告。
First, it sets the `currentAddIndex` by taking whatever it
currently is plus one, dividing that by the length of the `AdItem` array, and
using the _remainder_ as the new `currentAddIndex` value. Then, it uses that
value to select an `adItem` from the array.
(译注:循环选取算法)首先,它把`currentAddIndex`递增一,然后用它除以`AdItem`数组长度的*余数*作为新的`currentAddIndex`的值,
最后用这个值来从数组中选取一个`adItem`。
</div>
@ -145,33 +226,55 @@ After `loadComponent()` selects an ad, it uses `ComponentFactoryResolver`
to resolve a `ComponentFactory` for each specific component.
The `ComponentFactory` then creates an instance of each component.
在`loadComponent()`选取了一个广告之后,它使用`ComponentFactoryResolver`来为每个具体的组件解析出一个`ComponentFactory`。
然后`ComponentFactory`会为每一个组件创建一个实例。
Next, you're targeting the `viewContainerRef` that
exists on this specific instance of the component. How do you know it's
this specific instance? Because it's referring to `adHost` and `adHost` is the
directive you set up earlier to tell Angular where to insert dynamic components.
接下来,我们要把`viewContainerRef`指向这个组件的现有实例。但我们怎么才能找到这个实例呢?
很简单,因为它指向了`adHost`,而这个`adHost`就是我们以前设置过的指令用来告诉Angular该把动态组件插入到什么位置。
As you may recall, `AdDirective` injects `ViewContainerRef` into its constructor.
This is how the directive accesses the element that you want to use to host the dynamic component.
回忆一下,`AdDirective`曾在它的构造函数中注入了一个`ViewContainerRef`。
因此这个指令可以访问到这个被我们用作动态组件宿主的元素。
To add the component to the template, you call `createComponent()` on `ViewContainerRef`.
要把这个组件添加到模板中,我们可以调用`ViewContainerRef`的`createComponent()`。
The `createComponent()` method returns a reference to the loaded component.
Use that reference to interact with the component by assigning to its properties or calling its methods.
`createComponent()`方法返回一个引用,指向这个刚刚加载的组件。
使用这个引用就可以与该组件进行交互,比如设置它的属性或调用它的方法。
{@a selector-references}
#### Selector references
#### 对选择器的引用
Generally, the Angular compiler generates a `ComponentFactory`
for any component referenced in a template. However, there are
no selector references in the templates for
dynamically loaded components since they load at runtime.
通常Angular编译器会为模板中所引用的每个组件都生成一个`ComponentFactory`类。
但是,对于动态加载的组件,模板中不会出现对它们的选择器的引用。
To ensure that the compiler still generates a factory,
add dynamically loaded components to the `NgModule`'s `entryComponents` array:
要想确保编译器照常生成工厂类,就要把这些动态加载的组件添加到`NgModule`的`entryComponents`数组中:
<code-example path="cb-dynamic-component-loader/src/app/app.module.ts" region="entry-components" title="src/app/app.module.ts (entry components)" linenums="false">
</code-example>
@ -183,11 +286,17 @@ add dynamically loaded components to the `NgModule`'s `entryComponents` array:
### A common _AdComponent_ interface
### 公共的`AdComponent`接口
In the ad banner, all components implement a common `AdComponent` interface to
standardize the API for passing data to the components.
在广告条中,所有组件都实现了一个公共接口`AdComponent`它定义了一个标准化的API让我们把数据传给组件。
Here are two sample components and the `AdComponent` interface for reference:
下面就是两个范例组件及其`AdComponent`接口:
<code-tabs>
@ -211,7 +320,13 @@ Here are two sample components and the `AdComponent` interface for reference:
### Final ad banner
The final ad banner looks like this:
### 最终的广告栏
The final ad banner looks like this:
最终的广告栏是这样的:
<figure class='image-display'>
<img src="assets/images/cookbooks/dynamic-component-loader/ads.gif" alt="Ads"></img>
@ -219,4 +334,6 @@ Here are two sample components and the `AdComponent` interface for reference:
See the <live-example name="cb-dynamic-component-loader"></live-example>.
See the <live-example name="cb-dynamic-component-loader"></live-example>.
参见<live-example name="cb-dynamic-component-loader"></live-example>

View File

@ -1,55 +1,91 @@
@title
Dynamic Forms
动态表单
@intro
Render dynamic forms with FormGroup.
用 FormGroup 渲染动态表单
@description
Building handcrafted forms can be costly and time-consuming,
especially if you need a great number of them, they're similar to each other, and they change frequently
Building handcrafted forms canbe costly and time-consuming,
especially if you need a great number of them, they're similar to each other, and they change frequently
to meet rapidly changing business and regulatory requirements.
It may be more economical to create the forms dynamically, based on
metadata that describes the business object model.
有时候手动编写和维护表单所需工作量和时间会过大。特别是在需要编写大量表单时。表单都很相似,而且随着业务和监管需求的迅速变化,表单也要随之变化,这样维护的成本过高。
This cookbook shows you how to use `formGroup` to dynamically
render a simple form with different control types and validation.
It's a primitive start.
It may be more economical to create the forms dynamically, based on metadata that describes the business object model.
基于业务对象模型的元数据,动态创建表单可能会更划算。
This cookbook shows you how to use `formGroup` to dynamically render a simple form with different control types and validation.
It's a primitive start.
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
All such greatness has humble beginnings.
The example in this cookbook is a dynamic form to build an
online application experience for heroes seeking employment.
在此烹饪宝典中,我们会展示如何利用`formGroup`来动态渲染一个简单的表单,包括各种控件类型和验证规则。
这个起点很简陋,但可以在这个基础上添加丰富多彩的问卷问题、更优美的渲染以及更卓越的用户体验。
The example in this cookbook is a dynamic form to build an online application experience for heroes seeking employment.
The agency is constantly tinkering with the application process.
You can create the forms on the fly *without changing the application code*.
You can create the forms on the fly *without changing the application code*.
在本例中,我们使用动态表单,为正在找工作的英雄们创建一个在线申请表。英雄管理局会不断修改申请流程,我们要在*不修改应用代码*的情况下,动态创建这些表单。
{@a toc}
# Contents
# 目录
* [Bootstrap](guide/dynamic-form#bootstrap)
[程序启动](guide/dynamic-form#bootstrap)
* [Question model](guide/dynamic-form#object-model)
[问卷问题模型](guide/dynamic-form#object-model)
* [Question form components](guide/dynamic-form#form-component)
[问卷表单组件](guide/dynamic-form#form-component)
* [Questionnaire data](guide/dynamic-form#questionnaire-data)
[问卷数据](guide/dynamic-form#questionnaire-data)
* [Dynamic template](guide/dynamic-form#dynamic-template)
[动态模板](guide/dynamic-form#dynamic-template)
See the <live-example name="cb-dynamic-form"></live-example>.
**参见<live-example name="cb-dynamic-form"></live-example>**。
{@a bootstrap}
## Bootstrap
## 程序启动
Start by creating an `NgModule` called `AppModule`.
让我们从创建一个名叫`AppModule`的`NgModule`开始。
This cookbook uses [reactive forms](guide/reactive-forms).
这个烹饪书使用[响应式表单](guide/reactive-forms)。
Reactive forms belongs to a different `NgModule` called `ReactiveFormsModule`,
so in order to access any reactive forms directives, you have to import
`ReactiveFormsModule` from the `@angular/forms` library.
响应式表单属于另外一个叫做`ReactiveFormsModule`的`NgModule`,所以,为了使用响应式表单类的指令,我们得从`@angular/forms`库中引入`ReactiveFormsModule`模块。
Bootstrap the `AppModule` in `main.ts`.
我们在`main.ts`中启动`AppModule`。
<code-tabs>
@ -68,12 +104,18 @@ Bootstrap the `AppModule` in `main.ts`.
## Question model
## 问卷问题模型
The next step is to define an object model that can describe all scenarios needed by the form functionality.
The hero application process involves a form with a lot of questions.
The _question_ is the most fundamental object in the model.
第一步是定义一个对象模型,用来描述所有表单功能需要的场景。英雄的申请流程涉及到一个包含很多问卷问题的表单。问卷问题是最基础的对象模型。
The following `QuestionBase` is a fundamental question class.
下面是我们建立的最基础的问卷问题基类,名叫`QuestionBase`。
<code-example path="cb-dynamic-form/src/app/question-base.ts" title="src/app/question-base.ts">
@ -86,9 +128,13 @@ that represent textbox and dropdown questions.
The idea is that the form will be bound to specific question types and render the
appropriate controls dynamically.
在这个基础上,我们派生出两个新类`TextboxQuestion` 和 `DropdownQuestion`,分别代表文本框和下拉框。这么做的初衷是,表单能动态绑定到特定的问卷问题类型,并动态渲染出合适的控件。
`TextboxQuestion` supports multiple HTML5 types such as text, email, and url
via the `type` property.
`TextboxQuestion`可以通过`type`属性来支持多种HTML5元素类型比如文本、邮件、网址等。
<code-example path="cb-dynamic-form/src/app/question-textbox.ts" title="src/app/question-textbox.ts" linenums="false">
@ -98,6 +144,8 @@ via the `type` property.
`DropdownQuestion` presents a list of choices in a select box.
`DropdownQuestion`表示一个带可选项列表的选择框。
<code-example path="cb-dynamic-form/src/app/question-dropdown.ts" title="src/app/question-dropdown.ts" linenums="false">
@ -109,6 +157,10 @@ Next is `QuestionControlService`, a simple service for transforming the question
In a nutshell, the form group consumes the metadata from the question model and
allows you to specify default values and validation rules.
接下来,我们定义了`QuestionControlService`,一个可以把问卷问题转换为`FormGroup`的服务。
简而言之,这个`FormGroup`使用问卷模型的元数据,并允许我们设置默认值和验证规则。
<code-example path="cb-dynamic-form/src/app/question-control.service.ts" title="src/app/question-control.service.ts" linenums="false">
@ -117,11 +169,19 @@ allows you to specify default values and validation rules.
{@a form-component}
## Question form components
## 问卷表单组件
Now that you have defined the complete model you are ready
to create components to represent the dynamic form.
现在我们已经有一个定义好的完整模型了,接着就可以开始创建一个展现动态表单的组件。
`DynamicFormComponent` is the entry point and the main container for the form.
`DynamicFormComponent`是表单的主要容器和入口点。
`DynamicFormComponent` is the entry point and the main container for the form.
<code-tabs>
@ -142,6 +202,9 @@ The `<df-question>` tag matches the `DynamicFormQuestionComponent`,
the component responsible for rendering the details of each _individual_
question based on values in the data-bound question object.
它代表了问卷问题列表,每个问题都被绑定到一个`<df-question>`组件元素。
`<df-question>`标签匹配到的是组件`DynamicFormQuestionComponent`,该组件的职责是根据各个问卷问题对象的值来动态渲染表单控件。
<code-tabs>
@ -161,25 +224,41 @@ Notice this component can present any type of question in your model.
You only have two types of questions at this point but you can imagine many more.
The `ngSwitch` determines which type of question to display.
请注意,这个组件能代表模型里的任何问题类型。目前,还只有两种问题类型,但可以添加更多类型。可以用`ngSwitch`决定显示哪种类型的问题。
In both components you're relying on Angular's **formGroup** to connect the template HTML to the
underlying control objects, populated from the question model with display and validation rules.
在这两个组件中我们依赖Angular的**formGroup**来把模板HTML和底层控件对象连接起来该对象从问卷问题模型里获取渲染和验证规则。
`formControlName` and `formGroup` are directives defined in
`ReactiveFormsModule`. The templates can access these directives
directly since you imported `ReactiveFormsModule` from `AppModule`.
`formControlName`和`formGroup`是在`ReactiveFormsModule`中定义的指令。我们之所以能在模板中使用它们,是因为我们往`AppModule`中导入了`ReactiveFormsModule`。
{@a questionnaire-data}
## Questionnaire data
## 问卷数据
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
`DynamicForm`期望得到一个问题列表,该列表被绑定到`@Input() questions`属性。
The set of questions you've defined for the job application is returned from the `QuestionService`.
In a real app you'd retrieve these questions from storage.
`QuestionService`会返回为工作申请表定义的那组问题列表。在真实的应用程序环境中,我们会从数据库里获得这些问题列表。
The key point is that you control the hero job application questions
entirely through the objects returned from `QuestionService`.
Questionnaire maintenance is a simple matter of adding, updating,
and removing objects from the `questions` array.
关键是,我们完全根据`QuestionService`返回的对象来控制英雄的工作申请表。
要维护这份问卷,只要非常简单的添加、更新和删除`questions`数组中的对象就可以了。
<code-example path="cb-dynamic-form/src/app/question.service.ts" title="src/app/question.service.ts">
@ -190,6 +269,8 @@ directly since you imported `ReactiveFormsModule` from `AppModule`.
Finally, display an instance of the form in the `AppComponent` shell.
最后,在`AppComponent`里显示出表单。
<code-example path="cb-dynamic-form/src/app/app.component.ts" title="app.component.ts">
@ -198,28 +279,43 @@ Finally, display an instance of the form in the `AppComponent` shell.
{@a dynamic-template}
## Dynamic Template
Although in this example you're modelling a job application for heroes, there are
no references to any specific hero question
outside the objects returned by `QuestionService`.
## 动态模板
Although in this example you're modelling a job application for heroes, there are no references to any specific hero question
outside the objects returned by `QuestionService`.
在这个例子中,虽然我们是在为英雄的工作申请表建模,但是除了`QuestionService`返回的那些对象外,没有其它任何地方是与英雄有关的。
This is very important since it allows you to repurpose the components for any type of survey
as long as it's compatible with the *question* object model.
The key is the dynamic data binding of metadata used to render the form
without making any hardcoded assumptions about specific questions.
as long as it's compatible with the *question* object model.
The key is the dynamic data binding of metadata used to render the form
without making any hardcoded assumptions about specific questions.
In addition to control metadata, you are also adding validation dynamically.
这点非常重要,因为只要与*问卷*对象模型兼容,就可以在任何类型的调查问卷中复用这些组件。
这里的关键是用到元数据的动态数据绑定来渲染表单,对问卷问题没有任何硬性的假设。除控件的元数据外,还可以动态添加验证规则。
The *Save* button is disabled until the form is in a valid state.
When the form is valid, you can click *Save* and the app renders the current form values as JSON.
This proves that any user input is bound back to the data model.
Saving and retrieving the data is an exercise for another time.
表单验证通过之前,*保存*按钮是禁用的。验证通过后,就可以点击*保存*按钮程序会把当前值渲染成JSON显示出来。
这表明任何用户输入都被传到了数据模型里。至于如何储存和提取数据则是另一话题了。
The final form looks like this:
完整的表单是这样的:
<figure class='image-display'>
<img src="assets/images/cookbooks/dynamic-form/dynamic-form.png" alt="Dynamic-Form"></img>
</figure>
[Back to top](guide/dynamic-form#top)
[Back to top](guide/dynamic-form#top)
[回到顶部](guide/dynamic-form#top)

View File

@ -1,8 +1,8 @@
@title
Form Validation
表单验证
@intro
Validate user's form entries.
验证用户在表单中的输入
@description
@ -12,9 +12,14 @@ Validate user's form entries.
Improve overall data quality by validating user input for accuracy and completeness.
我们可以通过验证用户输入的准确性和完整性,来增强整体数据质量。
This cookbook shows how to validate user input in the UI and display useful validation messages
using first the template-driven forms and then the reactive forms approach.
在本烹饪书中,我们展示在界面中如何验证用户输入,并显示有用的验证信息,先使用模板驱动表单方式,再使用响应式表单方式。
<div class="l-sub-section">
@ -22,6 +27,8 @@ using first the template-driven forms and then the reactive forms approach.
Read more about these choices in the [Forms](guide/forms)
and the [Reactive Forms](guide/reactive-forms) guides.
参见[表单](guide/forms)和[响应式表单](guide/reactive-forms)了解关于这些选择的更多知识。
</div>
@ -32,36 +39,75 @@ and the [Reactive Forms](guide/reactive-forms) guides.
## Contents
## 目录
* [Simple template-driven forms](guide/form-validation#template1)
[简单的模板驱动表单](guide/form-validation#template1)
* [Template-driven forms with validation messages in code](guide/form-validation#template2)
[代码中带验证信息的模板驱动表单](guide/form-validation#template2)
* [Component Class](guide/form-validation#component-class)
[组件类](guide/form-validation#component-class)
* [The benefits of messages in code](guide/form-validation#improvement)
[在代码中写消息的优点](guide/form-validation#improvement)
* [`FormModule` and template-driven forms](guide/form-validation#formmodule)
[`FormModule`和模板驱动表单](guide/form-validation#formmodule)
* [Reactive forms with validation in code](guide/form-validation#reactive)
[代码中带验证消息的响应式表单](guide/form-validation#reactive)
* [Switch to the `ReactiveFormsModule`](guide/form-validation#reactive-forms-module)
[切换成`ReactiveFormsModule`](guide/form-validation#reactive-forms-module)
* [Component template](guide/form-validation#reactive-component-template)
[组件模板](guide/form-validation#reactive-component-template)
* [Component class](guide/form-validation#reactive-component-class)
[组件类](guide/form-validation#reactive-component-class)
* [`FormBuilder` declaration](guide/form-validation#formbuilder)
[`FormBuilder`声明](guide/form-validation#formbuilder)
* [Committing hero value changes](guide/form-validation#committing-changes)
[提交对英雄值的更改](guide/form-validation#committing-changes)
* [Custom validation](guide/form-validation#custom-validation)
[自定义验证器](guide/form-validation#custom-validation)
* [Custom validation directive](guide/form-validation#custom-validation-directive)
[自定义验证指令](guide/form-validation#custom-validation-directive)
* [Testing considerations](guide/form-validation#testing)
[测试方面的考虑](guide/form-validation#testing)
{@a live-example}
**Try the live example to see and download the full cookbook source code.**
<live-example name="cb-form-validation" embedded=true img="cookbooks/form-validation/plunker.png">
**查看在线例子,并下载整个烹饪书的源代码**
<live-example name="cb-form-validation" embedded=true img="cookbooks/form-validation/plunker.png">
在线例子
</live-example>
@ -72,23 +118,38 @@ and the [Reactive Forms](guide/reactive-forms) guides.
## Simple template-driven forms
## 简单的模板驱动表单
In the template-driven approach, you arrange
[form elements](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms_in_HTML) in the component's template.
在模板驱动表单方法中,你在组件的模板中组织[表单元素](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms_in_HTML)。
You add Angular form directives (mostly directives beginning `ng...`) to help
Angular construct a corresponding internal control model that implements form functionality.
In template-drive forms, the control model is _implicit_ in the template.
你可以添加Angular表单指令通常为以`ng`开头的指令来帮助Angular构建对应的内部控制模型以实现表单功能。
控制模型在模板中是**隐式**的。
To validate user input, you add [HTML validation attributes](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation)
to the elements. Angular interprets those as well, adding validator functions to the control model.
要验证用户输入,你添加[HTML验证属性](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation)到元素中。
Angular拦截这些元素添加验证器函数到控制模型中。
Angular exposes information about the state of the controls including
whether the user has "touched" the control or made changes and if the control values are valid.
Angular暴露关于控制状态的信息包括用户是否已经“触摸“了控制器或者用户已经作了更新和控制器的值是否还有效。
In this first template validation example,
notice the HTML that reads the control state and updates the display appropriately.
Here's an excerpt from the template HTML for a single input control bound to the hero name:
在第一个模板验证例子中我们添加了更多HTML来读取控制器状态并适当更新显示。
下面是模板HTML中提取的一个绑定到英雄名字的输入框控制器
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="name-with-error-msg" title="template/hero-form-template1.component.html (Hero name)" linenums="false">
</code-example>
@ -97,26 +158,45 @@ Here's an excerpt from the template HTML for a single input control bound to the
Note the following:
请注意以下几点:
* The `<input>` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`.
`<input>`元素带有一些HTML验证属性`required`、`minlength` 和 `maxlength`
* The `name` attribute of the input is set to `"name"` so Angular can track this input element and associate it
with an Angular form control called `name` in its internal control model.
我们把输入框的`name`属性设置为`"name"`这样Angular可以跟踪这个输入元素并将其内部控制器模型的一个名为`name`的Angular表单控制关联起来。
* The `[(ngModel)]` directive allows two-way data binding between the input box to the `hero.name` property.
我们使用`[(ngModel)]`指令,将输入框双向数据绑定到`hero.name`属性。
* The template variable (`#name`) has the value `"ngModel"` (always `ngModel`).
This gives you a reference to the Angular `NgModel` directive
associated with this control that you can use _in the template_
to check for control states such as `valid` and `dirty`.
我们将模板变量(`#name`)赋值为`"ngModel"` (总是 `ngModel`)。
它为我们提供了与这个控制器关联的Angular `NgModel`指令的引用,我们在模板中使用它,以检查控制器状态,比如`valid`和`dirty`。
* The `*ngIf` on the `<div>` element reveals a set of nested message `divs` but only if there are "name" errors and
the control is either `dirty` or `touched`.
`<div>`元素的`*ngIf`揭露了一套嵌套消息`divs`但是只在有“name”错误和控制器为`dirty`或者`touched`。
* Each nested `<div>` can present a custom message for one of the possible validation errors.
There are messages for `required`, `minlength`, and `maxlength`.
每个嵌套的`<div>`为其中一个可能出现的验证错误显示一条自定义消息。我们已经为`required`、`minlength`、和 `maxlength`准备了消息。
The full template repeats this kind of layout for each data entry control on the form.
整个模板为表单上的每种数据输入控制器重复这种布局。
{@a why-check}
@ -126,11 +206,18 @@ The full template repeats this kind of layout for each data entry control on the
#### Why check _dirty_ and _touched_?
#### 为何检查**dirty**和**touched**
The app shouldn't show errors for a new hero before the user has had a chance to edit the value.
The checks for `dirty` and `touched` prevent premature display of errors.
当用户创建一个新英雄时,在还没有机会输入之前,我们不应该显示任何错误。
检查`dirty`和`touched`防止了这种过早的错误显示。
Learn about `dirty` and `touched` in the [Forms](guide/forms) guide.
参见[表单](guide/forms)章,学习关于`dirty`和`touched`的知识。
</div>
@ -138,6 +225,8 @@ Learn about `dirty` and `touched` in the [Forms](guide/forms) guide.
The component class manages the hero model used in the data binding
as well as other code to support the view.
组件类管理用于数据绑定的英雄模型,它还有其他支持视图的代码。
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.ts" region="class" title="template/hero-form-template1.component.ts (class)">
@ -147,8 +236,12 @@ as well as other code to support the view.
Use this template-driven validation technique when working with static forms with simple, standard validation rules.
在处理简单的、拥有标准验证规则的静态表单时,使用这种模板驱动验证方法。
Here are the complete files for the first version of `HeroFormTemplateCompononent` in the template-driven approach:
下面是第一个版本的使用模板驱动方法的`HeroFormTemplateComponent`
<code-tabs>
@ -170,22 +263,37 @@ Here are the complete files for the first version of `HeroFormTemplateCompononen
## Template-driven forms with validation messages in code
## 验证消息在代码中的模板驱动表单
While the layout is straightforward,
there are obvious shortcomings with the way it's handling validation messages:
虽然布局很直观,但是我们处理验证消息的方法有明显的缺陷:
* It takes a lot of HTML to represent all possible error conditions.
This gets out of hand when there are many controls and many validation rules.
* There's a lot of JavaScript logic in the HTML.
它使用了很多HTML来表现所有可能出现的错误情况。
如果有太多控制器和太多验证规则,我们就失去了控制。
* There's a lot of JavaScript logic in theHTML.
我们不喜欢在HTML里面插入这么多JavaScript。
* The messages are static strings, hard-coded into the template.
It's easier to maintain _dynamic_ messages in the component class.
这些消息是静态的字符串,被硬编码到模板中。把这些动态消息放在代码中会更易于维护。
In this example, you can move the logic and the messages into the component with a few changes to
the template and component.
Here's the hero name again, excerpted from the revised template
(Template 2), next to the original version:
只需要对模板和组件做出一些修改,我们可以将逻辑和消息移到组件中。
Here's the hero name again, excerpted from the revised template (Template 2), next to the original version:
下面也是关于英雄名字的控制器从修改后的模板“Template 2”中抽取出来与原来的版本相比
<code-tabs>
@ -203,32 +311,54 @@ Here's the hero name again, excerpted from the revised template
The `<input>` element HTML is almost the same. There are noteworthy differences:
`<input>`元素的HTML几乎一样。但是下列有值得注意的区别
* The hard-code error message `<divs>` are gone.
硬编码的错误消息`<div>`消失了。
* There's a new attribute, `forbiddenName`, that is actually a custom validation directive.
It invalidates the control if the user enters "bob" in the name `<input>`([try it](guide/form-validation#live-example)).
See the [custom validation](guide/form-validation#custom-validation) section later in this cookbook for more information
on custom validation directives.
添加了一个新属性`forbiddenName`,它实际上是一个自定义验证指令。
如果用户名`<input>`中的任何地方输入“bob”该指令变将控制器标记为无效[试试](guide/form-validation#live-example))。
我们将在本烹饪书稍后的地方介绍[自定义验证指令](guide/form-validation#custom-validation)。
* The `#name` template variable is gone because the app no longer refers to the Angular control for this element.
模板变量`#name`消失了因为我们不再需要为这个元素引用Angular控制器。
* Binding to the new `formErrors.name` property is sufficent to display all name validation error messages.
绑定到新的`formErrors.name`属性,就可以处理所有名字验证错误信息了。
{@a component-class}
### Component class
### 组件类
The original component code for Template 1 stayed the same; however,
Template 2 requires some changes in the component. This section covers the code
necessary in Template 2's component class to acquire the Angular
form control and compose error messages.
原组件代码的模板一没变化只是模板二发生了变化。本节包括模板二的组件类以获取Angular表单控制器和撰写错误信息。
The first step is to acquire the form control that Angular created from the template by querying for it.
第一步是获取Angular通过查询模板而生成的表单控制器。
Look back at the top of the component template at the
`#heroForm` template variable in the `<form>` element:
回头看组件模板顶部,我们在`<form>`元素中设置`#heroForm`模板变量:
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="form-tag" title="template/hero-form-template1.component.html (form tag)" linenums="false">
</code-example>
@ -238,6 +368,10 @@ Look back at the top of the component template at the
The `heroForm` variable is a reference to the control model that Angular derived from the template.
Tell Angular to inject that model into the component class's `currentForm` property using a `@ViewChild` query:
`heroFrom`变量是Angular从模板衍生出来的控制模型的引用。
我们利用`@ViewChild`来告诉Angular注入这个模型到组件类的`currentForm`属性:
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="view-child" title="template/hero-form-template2.component.ts (heroForm)" linenums="false">
</code-example>
@ -246,18 +380,30 @@ Tell Angular to inject that model into the component class's `currentForm` prope
Some observations:
一些细节:
* Angular `@ViewChild` queries for a template variable when you pass it
the name of that variable as a string (`'heroForm'` in this case).
Angular的`@ViewChild`使用传入的模板变量的字符串名字(这里是`'heroForm'`),来查询对应的模板变量。
* The `heroForm` object changes several times during the life of the component, most notably when you add a new hero.
Periodically inspecting it reveals these changes.
`heroForm`对象在组件的生命周期内变化了好几次,最值得注意的是当我们添加一个新英雄时的变化。我们必须定期重新检测它。
* Angular calls the `ngAfterViewChecked` [lifecycle hook method](guide/lifecycle-hooks#afterview)
when anything changes in the view.
That's the right time to see if there's a new `heroForm` object.
当视图有任何变化时Angular调用`ngAfterViewChecked`[生命周期钩子方法](guide/lifecycle-hooks#afterview)。这是查看是否有新`heroForm`对象的最佳时机。
* When there _is_ a new `heroForm` model, `formChanged()` subscribes to its `valueChanges` _Observable_ property.
The `onValueChanged` handler looks for validation errors after every keystroke.
The `onValueChanged` handler looks for validation errors after every keystroke.
当出现新`heroForm`模型时,我们订阅它的`valueChanged`**可观察**属性。
`onValueChanged`处理器在每次用户键入后查找验证错误。
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="handler" title="template/hero-form-template2.component.ts (handler)" linenums="false">
@ -269,19 +415,41 @@ The `onValueChanged` handler interprets user data entry.
The `data` object passed into the handler contains the current element values.
The handler ignores them. Instead, it iterates over the fields of the component's `formErrors` object.
`onValueChanged`处理器拦截用户数据输入。
包含当前元素值得`data`对象被传入处理器。
处理器忽略它们。相反,它迭代组件的`formErrors`对象。
The `formErrors` is a dictionary of the hero fields that have validation rules and their current error messages.
Only two hero properties have validation rules, `name` and `power`.
The messages are empty strings when the hero data are valid.
`formErrors`是一个词典,包含了拥有验证规则和当前错误消息的英雄控件。
只有两个英雄属性有验证规则,`name`和`power`。
当英雄数据有效时,这些消息的值为空字符串。
For each field, the `onValueChanged` handler does the following:
对于每个字段,这个`onValueChanged`处理器会做这些:
* Clears the prior error message, if any.
清除以前的错误信息(如果有的话)
* Acquires the field's corresponding Angular form control.
获取控件对应的Angular表单控制器
* If such a control exists _and_ it's been changed ("dirty")
_and_ it's invalid, the handler composes a consolidated error message for all of the control's errors.
如果这样的控制器存在并且它被更新过“dirty”**并且**它无效,处理器就会为所有控制器的错误合成一条错误消息。
Next, the component needs some error messages of course&mdash;a set for each validated property with
one message per validation rule:
很显然,我们需要一些错误消息,每个验证的属性都需要一套,每个验证规则需要一条消息:
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="messages" title="template/hero-form-template2.component.ts (messages)" linenums="false">
</code-example>
@ -290,47 +458,74 @@ one message per validation rule:
Now every time the user makes a change, the `onValueChanged` handler checks for validation errors and produces messages accordingly.
现在,每次用户作出变化时,`onValueChanged`处理器检查验证错误并按情况发出错误消息。
{@a improvement}
### The benefits of messages in code
### 在代码中写消息的优点
Clearly the template got substantially smaller while the component code got substantially larger.
It's not easy to see the benefit when there are just three fields and only two of them have validation rules.
很显然,模板变得小多了,组件代码变得大多了。当只有三个控件并且其中只有两个有验证规则时,我们很难看出好处。
Consider what happens as the number of validated
fields and rules increases.
In general, HTML is harder to read and maintain than code.
The initial template was already large and threatening to get rapidly worse
with the addition of more validation message `<div>` elements.
假设增加需要验证的控件和规则后会怎么样。
通常HTML比代码更难阅读和维护。
初始的模板已经很大了,如果我们添加更多验证消息`<div>`,它会迅速变得更大。
After moving the validation messaging to the component,
the template grows more slowly and proportionally.
Each field has approximately the same number of lines no matter its number of validation rules.
The component also grows proportionally, at the rate of one line per validated field
and one line per validation message.
将验证消息移到组件后,模板的增长变得更加缓慢,幅度也小一些。
不管有多少个验证规则,每个控件的行数是差不多的。
组件也按比例增长,每增加一个控件增加一行,每个验证消息一行。
Both trends are manageable.
两条线容易维护。
Now that the messages are in code, you have more flexibility and can compose messages more efficiently.
You can refactor the messages out of the component, perhaps to a service class that retrieves them from the server.
In short, there are more opportunities to improve message handling now that text and logic have moved from template to code.
现在消息在代码中,我们有更多的灵活度。我们更加智能的撰写消息。
我们可以将消息重构出组件,比如到一个服务类,从服务端获取消息。
简而言之,有很多机会增强消息处理,因为文本和逻辑都已经从模板移到代码中。
{@a formmodule}
### _FormModule_ and template-driven forms
### _FormModule_ 和模板驱动表单
Angular has two different forms modules&mdash;`FormsModule` and
`ReactiveFormsModule`&mdash;that correspond with the
two approaches to form development. Both modules come
from the same `@angular/forms` library package.
Angular有两种不同的表单模块 - `FormsModule`和`ReactiveFormsModule` - 它们与表单开发的两种方法对应。
两种模块都从同一个`@angular/forms`库。
You've been reviewing the "Template-driven" approach which requires the `FormsModule`.
Here's how you imported it in the `HeroFormTemplateModule`.
我们一直在探讨**模板驱动**方法,它需要`FormsModule`。下面是如何在`HeroFormTemplateModule`中导入它:
<code-example path="cb-form-validation/src/app/template/hero-form-template.module.ts" title="template/hero-form-template.module.ts" linenums="false">
@ -343,10 +538,14 @@ Here's how you imported it in the `HeroFormTemplateModule`.
This guide hasn't talked about the `SharedModule` or its `SubmittedComponent` which appears at the bottom of every
form template in this cookbook.
form template in this cookbook.
我们还没有讲`SharedModule`或者它的`SubmittedComponent`,它们出现在本烹饪书的每一个表单模板中。
They're not germane to the validation story. Look at the [live example](guide/form-validation#live-example) if you're interested.
它们与表单验证没有紧密的关系。如果你感兴趣,参见[在线例子](guide/form-validation#live-example)。
</div>
@ -358,33 +557,63 @@ They're not germane to the validation story. Look at the [live example](guide/fo
## Reactive forms with validation in code
## 在代码中验证响应式表单
In the template-driven approach, you markup the template with form elements, validation attributes,
and `ng...` directives from the Angular `FormsModule`.
At runtime, Angular interprets the template and derives its _form control model_.
**Reactive Forms** takes a different approach.
在模板驱动方法中你在模板中标出表单元素、验证属性和Angular`FormsModule`中的`ng...`指令。
在运行时间Angular解释模板并从**表单控制器模型**衍生它。
**Reactive Forms** takes a different approach.
You create the form control model in code. You write the template with form elements
and `form...` directives from the Angular `ReactiveFormsModule`.
At runtime, Angular binds the template elements to your control model based on your instructions.
**响应式表单**采用不同的方法。
你在代码中创建表单控制器模型并用表单元素和来自Angular `ReactiveFormsModule`中的`form...`指令来编写模板。
在运行时间Angular根据你的指示绑定模板元素到你的控制器模型。
This approach requires a bit more effort. *You have to write the control model and manage it*.
这个方法需要做一些额外的工作。*你必须编写并管理控制器模型**。
This allows you to do the following:
这可以让你:
* Add, change, and remove validation functions on the fly.
随时添加、修改和删除验证函数
* Manipulate the control model dynamically from within the component.
在组件内动态操纵控制器模型
* [Test](guide/form-validation#testing) validation and control logic with isolated unit tests.
使用孤立单元测试来[测试](guide/form-validation#testing)验证和控制器逻辑
The following cookbook sample re-writes the hero form in _reactive forms_ style.
第三个烹饪书例子用**响应式表单**风格重新编写英雄表格。
{@a reactive-forms-module}
### Switch to the _ReactiveFormsModule_
### 切换到_ReactiveFormsModule_
The reactive forms classes and directives come from the Angular `ReactiveFormsModule`, not the `FormsModule`.
The application module for the reactive forms feature in this sample looks like this:
响应式表单类和指令来自于Angular的`ReactiveFormsModule`,不是`FormsModule`。
本例中,应用模块的“响应式表单”特性是这样的:
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.module.ts" title="src/app/reactive/hero-form-reactive.module.ts" linenums="false">
</code-example>
@ -394,16 +623,24 @@ The application module for the reactive forms feature in this sample looks like
The reactive forms feature module and component are in the `src/app/reactive` folder.
Focus on the `HeroFormReactiveComponent` there, starting with its template.
“响应式表单”特性模块和组件在`app/reactive`目录。
让我们关注那里的`HeroFormReactiveComponent`,先看它的模板。
{@a reactive-component-template}
### Component template
### 组件模板
Begin by changing the `<form>` tag so that it binds the Angular `formGroup` directive in the template
to the `heroForm` property in the component class.
The `heroForm` is the control model that the component class builds and maintains.
我们先修改`<form>`标签让Angular的`formGroup`指令绑定到组件类的`heroForm`属性。
`heroForm`是组件类创建和维护的控制器模型。
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.html" region="form-tag" title="cb-form-validation/src/app/reactive/hero-form-reactive.component.html" linenums="false">
@ -414,6 +651,10 @@ The `heroForm` is the control model that the component class builds and maintain
Next, modify the template HTML elements to match the _reactive forms_ style.
Here is the "name" portion of the template again, revised for reactive forms and compared with the template-driven version:
接下来我们修改模板HTML元素来匹配**响应式表单**样式。
下面又是“name”部分的模板响应式表单修改版本和模板驱动版本的比较
<code-tabs>
<code-pane title="hero-form-reactive.component.html (name #3)" path="cb-form-validation/src/app/reactive/hero-form-reactive.component.html" region="name-with-error-msg">
@ -429,12 +670,19 @@ Here is the "name" portion of the template again, revised for reactive forms and
Key changes are:
关键变化:
* The validation attributes are gone (except `required`) because
validating happens in code.
验证属性没有了(除了`required`),因为我们将在代码中验证。
* `required` remains, not for validation purposes (that's in the code),
but rather for css styling and accessibility.
保留`required`不是为了验证的目的验证在代码中而是为了CSS样式和可访问性。
<div class="l-sub-section">
@ -443,9 +691,13 @@ but rather for css styling and accessibility.
A future version of reactive forms will add the `required` HTML validation attribute to the DOM element
(and perhaps the `aria-required` attribute) when the control has the `required` validator function.
未来版本的响应式表单将会在控制器有`required`验证器函数时,添加`required` HTML验证属性到DOM元素也可能添加`aria-required`属性)。
Until then, apply the `required` attribute _and_ add the `Validator.required` function
to the control model, as you'll see below.
在此之前,添加`required`属性**以及**添加`Validator.required`函数到控制器模型,像我们下面这样做:
</div>
@ -454,10 +706,15 @@ to the control model, as you'll see below.
* The `formControlName` replaces the `name` attribute; it serves the same
purpose of correlating the input with the Angular form control.
* The two-way `[(ngModel)]` binding is gone.
`formControlName`替换了`name`属性它起到了关联输入框和Angular表单控制器的同样作用。
* The two-way `[(ngModel)]` binding is gone.
The reactive approach does not use data binding to move data into and out of the form controls.
That's all in code.
双向`[(ngModel)]`绑定消失了。
响应式表单方法不使用数据绑定从表单控制器移入和移出数据。我们在代码中做这些。
<div class="l-sub-section">
@ -465,6 +722,9 @@ That's all in code.
The retreat from data binding is a principle of the reactive paradigm rather than a technical limitation.
不适用表单数据绑定是响应式模式的原则,而非技术限制。
</div>
@ -474,14 +734,22 @@ The retreat from data binding is a principle of the reactive paradigm rather tha
### Component class
The component class is now responsible for defining and managing the form control model.
### 组件类
The component class is now responsible for defining and managing the form control model.
组件类现在负责定义和管理表单控制器模型。
Angular no longer derives the control model from the template so you can no longer query for it.
You can create the Angular form control model explicitly with
the help of the `FormBuilder` class.
You can create the Angular form control model explicitly with the help of the `FormBuilder`class.
Angular不再从模板衍生控制器模型所以我们不能再查询它。
我们利用`FormBuilder`来显式创建Angular表单控制器模型。
Here's the section of code devoted to that process, paired with the template-driven code it replaces:
下面是负责该进程的代码部分,与被它取代的模板驱动代码相比:
<code-tabs>
<code-pane title="reactive/hero-form-reactive.component.ts (FormBuilder)" path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="form-builder">
@ -498,15 +766,23 @@ Here's the section of code devoted to that process, paired with the template-dri
* Inject `FormBuilder` in a constructor.
我们注入`FormBuilder`到构造函数中。
* Call a `buildForm` method in the `ngOnInit` [lifecycle hook method](guide/lifecycle-hooks#hooks-overview)
because that's when you'll have the hero data. Call it again in the `addHero` method.
我们在`ngOnInit`[生命周期钩子方法](guide/lifecycle-hooks#hooks-overview)中调用`buildForm`方法,
因为这正是我们拥有英雄数据的时刻。我们将在`addHero`方法中再次调用它。
<div class="l-sub-section">
A real app would retrieve the hero asynchronously from a data service, a task best performed in the `ngOnInit` hook.
真实的应用很可能从数据服务异步获取英雄,这个任务最好在`ngOnInit`生命周期钩子中进行。
</div>
@ -516,23 +792,41 @@ Then it attaches the same `onValueChanged` handler (there's a one line differenc
to the form's `valueChanges` event and calls it immediately
to set error messages for the new control model.
`buildForm`方法使用`FormBuilder``fb`)来声明表单控制器模型。
然后它将相同的`onValueChanged`(有一行代码不一样)处理器附加到表单的`valueChanged`事件,
并立刻为新的控制器模型设置错误消息。
{@a formbuilder}
#### _FormBuilder_ declaration
#### _FormBuilder_声明
The `FormBuilder` declaration object specifies the three controls of the sample's hero form.
`FormBuilder`声明对象指定了本例英雄表单的三个控制器。
Each control spec is a control name with an array value.
The first array element is the current value of the corresponding hero field.
The optional second value is a validator function or an array of validator functions.
每个控制器的设置都是控制器名字和数组值。
第一个数组元素是英雄控件对应的当前值。
第二个值(可选)是验证器函数或者验证器函数数组。
Most of the validator functions are stock validators provided by Angular as static methods of the `Validators` class.
Angular has stock validators that correspond to the standard HTML validation attributes.
大多数验证器函数是Angular以`Validators`类的静态方法的形式提供的原装验证器。
Angular有一些原装验证器与标准HTML验证属性一一对应。
The `forbiddenNames` validator on the `"name"` control is a custom validator,
discussed in a separate [section below](guide/form-validation#custom-validation).
`"name"`控制器上的`forbiddenNames`验证器是自定义验证器,在下面单独的[小结](guide/form-validation#custom-validation)有所讨论。
<div class="l-sub-section">
@ -540,6 +834,8 @@ discussed in a separate [section below](guide/form-validation#custom-validation)
Learn more about `FormBuilder` in the [Introduction to FormBuilder](guide/reactive-forms#formbuilder) section of Reactive Forms guide.
到[响应式表单]的[FormBuilder介绍](guide/reactive-forms#formbuilder)部分,学习更多关于`FormBuilder`的知识。
</div>
@ -550,17 +846,32 @@ Learn more about `FormBuilder` in the [Introduction to FormBuilder](guide/reacti
#### Committing hero value changes
#### 提交英雄值的更新
In two-way data binding, the user's changes flow automatically from the controls back to the data model properties.
Reactive forms do not use data binding to update data model properties.
The developer decides _when and how_ to update the data model from control values.
在双向数据绑定时,用户的修改自动从控制器流向数据模型属性。
响应式表单不适用数据绑定来更新数据模型属性。
开发者决定**何时**与**如何**从控制器的值更新数据模型。
This sample updates the model twice:
本例更新模型两次:
1. When the user submits the form.
当用户提交标单时
1. When the user adds a new hero.
当用户添加新英雄时
The `onSubmit()` method simply replaces the `hero` object with the combined values of the form:
`onSubmit()`方法直接使用表单的值得合集来替换`hero`对象:
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="on-submit" title="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" linenums="false">
</code-example>
@ -574,12 +885,18 @@ The `onSubmit()` method simply replaces the `hero` object with the combined valu
This example is lucky in that the `heroForm.value` properties _just happen_ to
correspond _exactly_ to the hero data object properties.
本例非常“幸运”,因为`heroForm.value`属性**正好**与英雄数据对象属性对应。
</div>
The `addHero()` method discards pending changes and creates a brand new `hero` model object.
`addHero()`方法放弃未处理的变化,并创建一个崭新的`hero`模型对象。
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="add-hero" title="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" linenums="false">
</code-example>
@ -589,8 +906,13 @@ The `addHero()` method discards pending changes and creates a brand new `hero` m
Then it calls `buildForm()` again which replaces the previous `heroForm` control model with a new one.
The `<form>` tag's `[formGroup]` binding refreshes the page with the new control model.
然后它再次调用`buildForm`,用一个新对象替换了之前的`heroForm`控制器模型。
`<form>`标签的`[formGroup]`绑定使用这个新的控制器模型更新页面。
Here's the complete reactive component file, compared to the two template-driven component files.
下面是完整的响应式表单的组件文件,与两个模板驱动组件文件对比:
<code-tabs>
<code-pane title="reactive/hero-form-reactive.component.ts (#3)" path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts">
@ -616,6 +938,8 @@ Here's the complete reactive component file, compared to the two template-driven
Run the [live example](guide/form-validation#live-example) to see how the reactive form behaves,
and to compare all of the files in this cookbook sample.
运行[在线例子](guide/form-validation#live-example),查看响应式表单是的行为,并与本章中的例子文件作比较。
</div>
@ -626,12 +950,21 @@ and to compare all of the files in this cookbook sample.
## Custom validation
## 自定义验证
This cookbook sample has a custom `forbiddenNamevalidator()` function that's applied to both the
template-driven and the reactive form controls. It's in the `src/app/shared` folder
and declared in the `SharedModule`.
本烹饪书例子有一个自定义`forbiddenNameValidator`函数,在模板驱动和响应式表单中都有使用。
它在`app/shared`目录,在`SharedModule`中被声明。
Here's the `forbiddenNamevalidator()` function:
下面是`forbiddenNameValidator`函数:
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" title="shared/forbidden-name.directive.ts (forbiddenNameValidator)" linenums="false">
</code-example>
@ -641,24 +974,40 @@ Here's the `forbiddenNamevalidator()` function:
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name
and returns a validator function.
该函数其实是一个工厂函数,接受一个正则表达式,用来检测**指定**的禁止的名字,并返回验证器函数。
In this sample, the forbidden name is "bob";
the validator rejects any hero name containing "bob".
Elsewhere it could reject "alice" or any name that the configuring regular expression matches.
在本例中禁止的名字是“bob”
验证器拒绝任何带有“bob”的英雄名字。
在其他地方只要配置的正则表达式可以匹配上它可能拒绝“alice”或者任何其他名字。
The `forbiddenNameValidator` factory returns the configured validator function.
That function takes an Angular control object and returns _either_
null if the control value is valid _or_ a validation error object.
The validation error object typically has a property whose name is the validation key, `'forbiddenName'`,
and whose value is an arbitrary dictionary of values that you could insert into an error message (`{name}`).
`forbiddenNameValidator`工厂函数返回配置好的验证器函数。
该函数接受一个Angular控制器对象并在控制器值有效时返回null或无效时返回验证错误对象。
验证错误对象通常有一个名为验证秘钥(`forbiddenName`)的属性。其值为一个任意词典,我们可以用来插入错误信息(`{name}`)。
{@a custom-validation-directive}
### Custom validation directive
#### 自定义验证指令
In the reactive forms component, the `'name'` control's validator function list
has a `forbiddenNameValidator` at the bottom.
在响应式表单组件中,我们在`'name'`控制器的验证函数列表的底部添加了一个配置了的`forbiddenNameValidator`。
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="name-validators" title="reactive/hero-form-reactive.component.ts (name validators)" linenums="false">
</code-example>
@ -668,6 +1017,9 @@ has a `forbiddenNameValidator` at the bottom.
In the _template-driven_ example, the `<input>` has the selector (`forbiddenName`)
of a custom _attribute directive_, which rejects "bob".
在模板驱动组件的模板中我们在name的输入框元素中添加了自定义**属性指令**的选择器(`forbiddenName`并配置它来拒绝“bob”。
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.html" region="name-input" title="template/hero-form-template2.component.html (name input)" linenums="false">
</code-example>
@ -676,9 +1028,14 @@ of a custom _attribute directive_, which rejects "bob".
The corresponding `ForbiddenValidatorDirective` is a wrapper around the `forbiddenNameValidator`.
对应的`ForbiddenValidatorDirective`包装了`forbiddenNamevalidator`。
Angular `forms` recognizes the directive's role in the validation process because the directive registers itself
with the `NG_VALIDATORS` provider, a provider with an extensible collection of validation directives.
Angular表单接受指令在验证流程中的作用因为指令注册自己到`NG_VALIDATORS`提供商中,该提供商拥有可扩展的验证指令集。
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="directive-providers" title="shared/forbidden-name.directive.ts (providers)" linenums="false">
</code-example>
@ -687,6 +1044,9 @@ with the `NG_VALIDATORS` provider, a provider with an extensible collection of v
Here is the rest of the directive to help you get an idea of how it all comes together:
指令的其它部分是为了帮你理解它们是如何合作的:
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="directive" title="shared/forbidden-name.directive.ts (directive)">
</code-example>
@ -725,6 +1085,8 @@ This time, when you type “bob”, there's no "bob" error message.
For more information on attaching behavior to elements,
see [Attribute Directives](guide/attribute-directives).
参见[属性型指令](guide/attribute-directives)章节。
</div>
@ -736,20 +1098,34 @@ see [Attribute Directives](guide/attribute-directives).
## Testing Considerations
## 测试时的注意事项
You can write _isolated unit tests_ of validation and control logic in _Reactive Forms_.
我们可以为**响应式表单**的验证器和控制器逻辑编写**孤立单元测试**。
_Isolated unit tests_ probe the component class directly, independent of its
interactions with its template, the DOM, other dependencies, or Angular itself.
**孤立单元测试**直接检测组件类与组件和它的模板的交互、DOM、其他以来和Angular本省都无关。
Such tests have minimal setup, are quick to write, and easy to maintain.
They do not require the `Angular TestBed` or asynchronous testing practices.
这样的测试具有简单设置#,快速编写和容易维护的特征。它们不需要`Angular TestBed`或异步测试工序。
That's not possible with _template-driven_ forms.
The template-driven approach relies on Angular to produce the control model and
to derive validation rules from the HTML validation attributes.
You must use the `Angular TestBed` to create component test instances,
write asynchronous tests, and interact with the DOM.
这对**模板驱动**表单来说是不可能的。
模板驱动方法依靠Angular来生成控制器模型并从HTML验证属性中衍生验证规则。
你必须使用`Angular TestBed`来创建组件测试实例编写异步测试并与DOM交互。
While not difficult, this takes more time, work and
skill&mdash;factors that tend to diminish test code
coverage and quality.
coverage and quality.
虽然这种测试并不困难,但是它需要更多时间、工作和能力 - 这些因素往往会降低测试代码覆盖率和测试质量。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
@title
Hierarchical Dependency Injectors
多级依赖注入器
@intro
Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree.
Angular 的多级依赖注入系统支持与组件树并行的嵌套式注入器。
@description
@ -11,26 +11,44 @@ Angular's hierarchical dependency injection system supports nested injectors in
You learned the basics of Angular Dependency injection in the
[Dependency Injection](guide/dependency-injection) guide.
在[依赖注入](guide/dependency-injection)一章中,我们已经学过了 Angular 依赖注入的基础知识。
Angular has a _Hierarchical Dependency Injection_ system.
There is actually a tree of injectors that parallel an application's component tree.
You can reconfigure the injectors at any level of that component tree.
Angular 有一个*多级依赖注入系统*。
实际上,应用程序中有一个与组件树平行的注入器树(译注:平行是指结构完全相同且一一对应)。
我们可以在组件树中的任何级别上重新配置注入器,达到一些有趣和有用的效果。
This guide explores this system and how to use it to your advantage.
在本章中,我们将浏览这个体系,并告诉你如何善用它。
Try the <live-example></live-example>.
试试<live-example></live-example>.
## The injector tree
## 注入器树
In the [Dependency Injection](guide/dependency-injection) guide,
you learned how to configure a dependency injector and how to retrieve dependencies where you need them.
在[依赖注入](guide/dependency-injection)一章中,我们学过如何配置依赖注入器,以及如何在我们需要时用它获取依赖。
In fact, there is no such thing as ***the*** injector.
An application may have multiple injectors.
An Angular application is a tree of components. Each component instance has its own injector.
The tree of components parallels the tree of injectors.
实际上,没有***那个(唯一的)***注入器这回事,一个应用中可能有多个注入器。
一个 Angular 应用是一个组件树。每个组件实例都有自己的注入器!
组件的树与注入器的树平行。
<div class="l-sub-section">
@ -41,18 +59,25 @@ That's an implementation detail that improves efficiency.
You won't notice the difference and
your mental model should be that every component has its own injector.
组件的注入器可能是一个组件树中更高级的祖先注入器的*代理*。
但这只是提升效率的实现细节,我们不用在乎这点差异,在你的脑海里只要想象成每个组件都有自己的注入器就可以了。
</div>
Consider this guide's variation on the Tour of Heroes application.
At the top is the `AppComponent` which has some sub-components.
One of them is the `HeroesListComponent`.
The `HeroesListComponent` holds and manages multiple instances of the `HeroTaxReturnComponent`.
Consider this guide's variation on the Tour of Heroes application .
At the top is the `AppComponent` which has some sub- components.
One of them is the`HeroesListComponent`.
The `HeroesListComponent` holds and manages multiple instances of the `HeroTaxReturnComponent`.
The following diagram represents the state of the this guide's three-level component tree when there are three instances of `HeroTaxReturnComponent`
open simultaneously.
考虑《英雄指南》应用的一个简单变种。它的顶层是`AppComponent`组件,它有一些子组件。
`HeroesListComponent`组件保存和管理着`HeroTaxReturnComponent`的多个实例。
下图展示了当`HeroesCardComponent`的三个 `HeroTaxReturnComponent` 实例同时展开时的三级组件树状态。
<figure class='image-display'>
<img src="assets/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree" width="600"></img>
@ -62,12 +87,20 @@ open simultaneously.
### Injector bubbling
### 注入器冒泡
When a component requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.
If the component's injector lacks the provider, it passes the request up to its parent component's injector.
If that injector can't satisfy the request, it passes it along to *its* parent injector.
The requests keep bubbling up until Angular finds an injector that can handle the request or runs out of ancestor injectors.
If it runs out of ancestors, Angular throws an error.
当一个组件申请获得一个依赖时Angular 先尝试用该组件自己的注入器来满足它。
如果该组件的注入器没有找到对应的提供商,它就把这个申请转给它父组件的注入器来处理。
如果那个注入器也无法满足这个申请,它就继续转给*它的*父组件的注入器。
这个申请继续往上冒泡 —— 直到我们找到了一个能处理此申请的注入器或者超出了组件树中的祖先位置为止。
如果超出了组件树中的祖先还未找到Angular 就会抛出一个错误。
<div class="l-sub-section">
@ -75,7 +108,11 @@ If it runs out of ancestors, Angular throws an error.
You can cap the bubbling. An intermediate component can declare that it is the "host" component.
The hunt for providers will climb no higher than the injector for that host component.
This is a topic for another day.
This is a topic for another day.
我们还可以“盖住”这次冒泡。一个中层的组件可以声称自己是“宿主”组件。
向上查找提供商的过程会截止于这个“宿主”组件。
我们先保留这个问题,等改天再讨论这个选项。
</div>
@ -84,38 +121,67 @@ This is a topic for another day.
### Re-providing a service at different levels
### 在不同层级再次提供同一个服务
You can re-register a provider for a particular dependency token at multiple levels of the injector tree.
You don't *have* to re-register providers. You shouldn't do so unless you have a good reason.
But you *can*.
我们可以在注入器树中的多个层次上为指定的依赖令牌重新注册提供商。
但*并非必须*重新注册,事实上,虽然可以重新注册,但除非有很好的理由,否则不应该这么做。
As the resolution logic works upwards, the first provider encountered wins.
Thus, a provider in an intermediate injector intercepts a request for a service from something lower in the tree.
It effectively "reconfigures" and "shadows" a provider at a higher level in the tree.
服务解析逻辑会自下而上查找,碰到的第一个提供商会胜出。
因此,注入器树中间层注入器上的提供商,可以拦截来自底层的对特定服务的请求。
这导致它可以“重新配置”和者说“遮蔽”高层的注入器。
If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
All requests bubble up to the root <code>NgModule</code> injector that you configured with the `bootstrapModule` method.
如果我们只在顶级(通常是根模块`AppModule`),这三个注入器看起来将是“平面”的。
所有的申请都会冒泡到根<code>NgModule</code>进行处理,也就是我们在`bootstrapModule`方法中配置的那个。
## Component injectors
## 组件注入器
The ability to configure one or more providers at different levels opens up interesting and useful possibilities.
在不同层次上重新配置一个或多个提供商的能力,开启了一些既有趣又有用的可能性。
### Scenario: service isolation
### 场景:服务隔离
Architectural reasons may lead you to restrict access to a service to the application domain where it belongs.
出于架构方面的考虑,可能会让你决定把一个服务限制到只能在它所属的特定领域中访问。
The guide sample includes a `VillainsListComponent` that displays a list of villains.
It gets those villains from a `VillainsService`.
本章的范例中包括一个`VillainsListComponent`,它显示一个反派的列表。
While you _could_ provide `VillainsService` in the root `AppModule` (that's where you'll find the `HeroesService`),
that would make the `VillainsService` available everywhere in the application, including the _Hero_ workflows.
虽然我们也可以在根模块`AppModule`中提供`VillainsService`(就像`HeroesService`那样),不过那样一来就会导致在整个应用中到处都能访问到`VillainsService`,包括在*英雄*工作流中。
If you later modified the `VillainsService`, you could break something in a hero component somewhere.
That's not supposed to happen but providing the service in the root `AppModule` creates that risk.
如果我们以后修改了`VillainsService`,那就可能会破坏英雄组件中的某些部分。
这可不妙,但是在根模块`AppModule`中提供这个服务可能会导致这种风险。
Instead, provide the `VillainsService` in the `providers` metadata of the `VillainsListComponent` like this:
我们可以换一种方案:在`VillainsListComponent`元数据的`providers`中提供`VillainsService`,就像这样:
<code-example path="hierarchical-dependency-injection/src/app/villains-list.component.ts" linenums="false" title="src/app/villains-list.component.ts (metadata)" region="metadata">
@ -127,26 +193,52 @@ By providing `VillainsService` in the `VillainsListComponent` metadata and nowhe
the service becomes available only in the `VillainsListComponent` and its sub-component tree.
It's still a singleton, but it's a singleton that exist solely in the _villain_ domain.
在`VillainsListComponent`的元数据中而不是其它地方提供`VillainsService`服务,该服务就会只在`VillainsListComponent`及其子组件树中可用。
它仍然是单例,但是这个单例只存在于*反派villain*这个领域中。
Now you know that a hero component can't access it. You've reduced your exposure to error.
现在,我们可以确信英雄组件不会访问它,因此减少了犯错误的机会。
### Scenario: multiple edit sessions
### 场景:多重编辑会话
Many applications allow users to work on several open tasks at the same time.
For example, in a tax preparation application, the preparer could be working on several tax returns,
switching from one to the other throughout the day.
很多应用允许用户同时进行多个任务。
比如,在纳税申报应用中,申报人可以打开多个报税单,随时可能从一个切换到另一个。
This guide demonstrates that scenario with an example in the Tour of Heroes theme.
Imagine an outer `HeroListComponent` that displays a list of super heroes.
本章要示范的场景仍然是基于《英雄指南》的。
想象一个外层的`HeroListComponent`,它显示一个超级英雄的列表。
To open a hero's tax return, the preparer clicks on a hero name, which opens a component for editing that return.
Each selected hero tax return opens in its own component and multiple returns can be open at the same time.
要打开一个英雄的报税单,申报者点击英雄名,它就会打开一个组件来编辑那个申报单。
每个选中的申报单都会在自己的组件中打开,并且可以同时打开多个申报单。
Each tax return component has the following characteristics:
每个报税单组件都有下列特征:
* Is its own tax return editing session.
属于它自己的报税单会话。
* Can change a tax return without affecting a return in another component.
可以修改一个报税单,而不会影响另一个组件中的申报单。
* Has the ability to save the changes to its tax return or cancel them.
能把所做的修改保存到它的报税单中,或者放弃它们。
<figure class='image-display'>
<img src="assets/images/devguide/dependency-injection/hid-heroes-anim.gif" width="400" alt="Heroes in action"></img>
@ -159,10 +251,19 @@ That would be a pretty easy task for a simple hero tax return.
In the real world, with a rich tax return data model, the change management would be tricky.
You might delegate that management to a helper service, as this example does.
实现方式之一就是让`HeroTaxReturnComponent`有逻辑来管理和还原那些更改。
这对于简单的报税单来说是很容易的。
不过,在现实世界中,报税单的数据模型非常复杂,对这些修改的管理可能不得不投机取巧。
于是我们可以把这种管理任务委托给一个辅助服务,就像这个例子中所做的。
Here is the `HeroTaxReturnService`.
It caches a single `HeroTaxReturn`, tracks changes to that return, and can save or restore it.
It also delegates to the application-wide singleton `HeroService`, which it gets by injection.
这是一个报税单服务`HeroTaxReturnService`。
它缓存了单条`HeroTaxReturn`,用于跟踪那个申报单的变更,并且可以保存或还原它。
它还委托给了全应用级的单例服务`HeroService`,它是通过依赖注入机制取得的。
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.service.ts" title="src/app/hero-tax-return.service.ts">
@ -172,6 +273,8 @@ It also delegates to the application-wide singleton `HeroService`, which it gets
Here is the `HeroTaxReturnComponent` that makes use of it.
下面是正在使用它的`HeroTaxReturnComponent`组件。
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" title="src/app/hero-tax-return.component.ts">
@ -184,13 +287,22 @@ The setter initializes the component's own instance of the `HeroTaxReturnService
The getter always returns what that service says is the current state of the hero.
The component also asks the service to save and restore this tax return.
我们通过输入属性得到*要编辑的报税单*我们把它实现成了读取器getter和设置器setter
设置器根据传进来的报税单初始化了组件自己的`HeroTaxReturnService`实例。
读取器总是返回该服务所存英雄的当前状态。
组件也会请求该服务来保存或还原这个报税单。
There'd be big trouble if _this_ service were an application-wide singleton.
Every component would share the same service instance.
Each component would overwrite the tax return that belonged to another hero.
What a mess!
这里有个大问题,那就是如果*这个*服务是一个全应用范围的单例,每个组件就都会共享同一个服务实例,每个组件也都会覆盖属于其他英雄的报税单,真是一团糟!
Look closely at the metadata for the `HeroTaxReturnComponent`. Notice the `providers` property.
但仔细看`HeroTaxReturnComponent`的元数据,注意`providers`属性。
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" linenums="false" title="src/app/hero-tax-return.component.ts (providers)" region="providers">
@ -203,6 +315,11 @@ Recall that every component _instance_ has its own injector.
Providing the service at the component level ensures that _every_ instance of the component gets its own, private instance of the service.
No tax return overwriting. No mess.
`HeroTaxReturnComponent`有它自己的`HeroTaxReturnService`提供商。
回忆一下,每个组件的*实例*都有它自己的注入器。
在组件级提供服务可以确保组件的*每个*实例都得到一个自己的、私有的服务实例。
报税单不会再被意外覆盖,这下清楚了。
<div class="l-sub-section">
@ -211,6 +328,9 @@ No tax return overwriting. No mess.
The rest of the scenario code relies on other Angular features and techniques that you can learn about elsewhere in the documentation.
You can review it and download it from the <live-example></live-example>.
该场景代码中的其它部分依赖另一些Angular的特性和技术我们将会在本文档的其它章节学到。
你可以到<live-example></live-example>查看代码和下载它。
</div>
@ -218,20 +338,33 @@ You can review it and download it from the <live-example></live-example>.
### Scenario: specialized providers
### 场景:专门的提供商
Another reason to re-provide a service is to substitute a _more specialized_ implementation of that service,
deeper in the component tree.
重新提供服务的另一个原因,是在组件树的深层中把该服务替换为一个*更特殊的*实现。
Consider again the Car example from the [Dependency Injection](guide/dependency-injection) guide.
Suppose you configured the root injector (marked as A) with _generic_ providers for
`CarService`, `EngineService` and `TiresService`.
再次考虑[依赖注入](guide/dependency-injection)一章中车辆Car的例子。
假设我们在根注入器代号A中配置了*通用的*提供商:`CarService`、`EngineService`和`TiresService`。
You create a car component (A) that displays a car constructed from these three generic services.
我们创建了一个车辆组件A它显示一个从另外三个通用服务构造出的车辆。
Then you create a child component (B) that defines its own, _specialized_ providers for `CarService` and `EngineService`
that have special capabilites suitable for whatever is going on in component (B).
然后我们创建一个子组件B它为`CarService`和`EngineService`定义了自己的*特殊的*提供商它们具有更特殊的能力适用于组件B的。
Component (B) is the parent of another component (C) that defines its own, even _more specialized_ provider for `CarService`.
组件B是另一个组件C的父组件而组件C又定义了自己的*更特殊的*`CarService`提供商。
<figure class='image-display'>
<img src="assets/images/devguide/dependency-injection/car-components.png" alt="car components" width="220"></img>
@ -241,10 +374,14 @@ Component (B) is the parent of another component (C) that defines its own, even
Behind the scenes, each component sets up its own injector with zero, one, or more providers defined for that component itself.
在幕后每个组件都有自己的注入器这个注入器带有为组件本身准备的0个、1个或多个提供商。
When you resolve an instance of `Car` at the deepest component (C),
its injector produces an instance of `Car` resolved by injector (C) with an `Engine` resolved by injector (B) and
`Tires` resolved by the root injector (A).
当我们在最深层的组件C解析`Car`的实例时它使用注入器C解析生成了一个`Car`的实例使用注入器B解析了`Engine`,而`Tires`则是由根注入器A解析的。
<figure class='image-display'>
<img src="assets/images/devguide/dependency-injection/injector-tree.png" alt="car injector tree" width="600"></img>
@ -259,5 +396,8 @@ its injector produces an instance of `Car` resolved by injector (C) with an `Eng
The code for this _cars_ scenario is in the `car.components.ts` and `car.services.ts` files of the sample
which you can review and download from the <live-example></live-example>.
*车辆*场景下的代码位于`car.components.ts`和`car.services.ts`文件中,这个例子你可以在<live-example></live-example>查看和下载。
</div>

View File

@ -1,8 +1,8 @@
@title
Internationalization (i18n)
国际化 (i18n)
@intro
Translate the app's template text into multiple languages.
把应用的模板文本翻译成多种语言。
@description
@ -12,38 +12,117 @@ Translate the app's template text into multiple languages.
Angular's _internationalization_ (_i18n_) tools help make your app available in multiple languages.
Angular的*国际化**i18n*)工具可以帮助我们使用多个语言发布应用。
# Contents
# 目录
* [Angular and i18n template translation](guide/i18n#angular-i18n)
[Angular和i18n模板翻译](guide/i18n#angular-i18n)
* [Mark text with the _i18n_ attribute](guide/i18n#i18n-attribute)
[把文本用_i18n_属性标记出来](guide/i18n#i18n-attribute)
* [Help the translator with a description and intent](guide/i18n#help-translator)
[通过添加描述和意图来帮助翻译者](guide/i18n#help-translator)
* [Translate text without creating an element](guide/i18n#no-element)
[在不创建元素的情况下翻译文本](guide/i18n#no-element)
* [Add _i18n-..._ translation attributes](guide/i18n#translate-attributes)
[添加 *i18n-...* 翻译属性](guide/i18n#translate-attributes)
* [Handle singular and plural](guide/i18n#cardinality)
[处理单数和复数](guide/i18n#cardinality)
* [Select among alternative texts](guide/i18n#select)
[在候选内容中做出选择](guide/i18n#select)
* [Create a translation source file with the **_ng-xi18n_ extraction tool**](guide/i18n#ng-xi18n)
[使用_ng-xi18n_工具创建翻译源文件](guide/i18n#ng-xi18n)
* [Other translation formats](guide/i18n#other-formats)
[其它翻译格式](guide/i18n#other-formats)
* [Other options](guide/i18n#ng-xi18n-options)
[其它选项](guide/i18n#ng-xi18n-options)
* [Add an `npm` script for convenience](guide/i18n#npm-i18n-script)
[添加`npm`脚本来便捷使用](guide/i18n#npm-i18n-script)
* [Translate text messages](guide/i18n#translate)
[翻译文本信息](guide/i18n#translate)
* [Create a localization folder](guide/i18n#localization-folder)
[创建本地化文件夹](guide/i18n#localization-folder)
* [Translate text nodes](guide/i18n#translate-text-nodes)
[翻译文本节点](guide/i18n#translate-text-nodes)
* [Translate `plural` and `select`](guide/i18n#translate-plural-select)
[翻译复数形式`plural`及选择项`select`](guide/i18n#translate-plural-select)
* [Translate `plural`](guide/i18n#translate-plural)
[翻译复数形式`plural`](guide/i18n#translate-plural)
* [Translate `select`](guide/i18n#translate-select)
[翻译`select`](guide/i18n#translate-select)
* [The app before translation](guide/i18n#app-pre-translation)
[翻译之前的应用](guide/i18n#app-pre-translation)
* [Merge the completed translation file into the app](guide/i18n#merge)
[把翻译完成的文件合并到应用中](guide/i18n#merge)
* [Merge with the JIT compiler](guide/i18n#jit)
[合并到JIT编译器中](guide/i18n#jit)
* [SystemJS text plugin](guide/i18n#text-plugin)
[SystemJS的文本插件](guide/i18n#text-plugin)
* [Create translation providers](guide/i18n#create-translation-providers)
[创建翻译提供商](guide/i18n#create-translation-providers)
* [Bootstrap the app with translation providers](guide/i18n#bootstrap-the-app)
[通过翻译提供商引导应用](guide/i18n#bootstrap-the-app)
* [Internationalization with the AOT compiler](guide/i18n#aot)
[AOT编译器的国际化](guide/i18n#aot)
* [Translation file maintenance and _id_ changes](guide/i18n#maintenance)
[翻译文件的维护和 *id* 变更](guide/i18n#maintenance)
Try this <live-example name="cb-i18n" title="i18n Example in Spanish">live example</live-example>
of a JIT-compiled app, translated into Spanish.
**试试** 这个翻译为西班牙语版JiT编译应用的<live-example name="cb-i18n">在线例子</live-example>
{@a angular-i18n}
@ -52,13 +131,20 @@ of a JIT-compiled app, translated into Spanish.
## Angular and _i18n_ template translation
## Angular和_i18n_模板翻译
Application internationalization is a challenging, many-faceted effort that
takes dedication and enduring commitment.
Angular's _i18n_ internationalization facilities can help.
应用程序国际化很具有挑战性,多方面的努力,需要持久的奉献和决心。
Angular的_i18n_国际化工具可以帮助你。
This page describes the _i18n_ tools available to assist translation of component template text
into multiple languages.
本章描述了一些_i18n_工具它们可以帮你把组件模板文本翻译成多种语言。
<div class="l-sub-section">
@ -68,6 +154,9 @@ into multiple languages.
Practitioners of _internationalization_ refer to a translatable text as a "_message_".
This page uses the words "_text_" and "_message_" interchangably and in the combination, "_text message_".
**国际化**工作者通常将一个可翻译的文本叫作“信息”。
本章使用了“文本”和“信息”,它们可以互换,也可以组合“文本信息”。
</div>
@ -75,19 +164,31 @@ This page uses the words "_text_" and "_message_" interchangably and in the comb
The _i18n_ template translation process has four phases:
_i18n_模板翻译流程有四个阶段
1. Mark static text messages in your component templates for translation.
在组件模板中标记需要翻译的静态文本信息。
1. An angular _i18n_ tool extracts the marked messages into an industry standard translation source file.
Angular的_i18n_工具将标记的信息提取到一个行业标准的翻译源文件。
1. A translator edits that file, translating the extracted text messages into the target language,
and returns the file to you.
翻译人员编辑该文件,翻译提取出来的文本信息到目标语言,并将该文件还给你。
1. The Angular compiler imports the completed translation files,
replaces the original messages with translated text, and generates a new version of the application
in the target language.
Angular编译器导入完成翻译的文件使用翻译的文本替换原始信息并生成新的目标语言版本的应用程序。
You need to build and deploy a separate version of the application for each supported language.
你可以为每种支持的语言构建和部署单独的应用程序版本。
{@a i18n-attribute}
@ -95,9 +196,14 @@ You need to build and deploy a separate version of the application for each supp
## Mark text with the _i18n_ attribute
## 使用_i18n_属性标记文本
The Angular `i18n` attribute is a marker for translatable content.
Place it on every element tag whose fixed text should be translated.
Angular的`i18n`属性是可翻译内容的标记。
将它放到每个固定文本需要翻译的元素标签中。
<div class="alert is-helpful">
@ -107,6 +213,10 @@ Place it on every element tag whose fixed text should be translated.
It's a custom _attribute_, recognized by Angular tools and compilers.
After translation, the compiler removes it.
`i18n`不是Angular指令。
它是一个自定义**属性**Angular工具和编译器认识它。
它将在完成翻译**之后**,被编译器移除。
</div>
@ -115,6 +225,9 @@ After translation, the compiler removes it.
In the accompanying sample, an `<h1>` tag displays a simple English language greeting
that you translate into Spanish:
在例子中,`<h1>`标签显示了一句简单的英文问候语,它将被翻译为西班牙语:
<code-example path="cb-i18n/src/app/app.component.1.html" region="greeting" title="src/app/app.component.html" linenums="false">
</code-example>
@ -123,6 +236,8 @@ that you translate into Spanish:
Add the `i18n` attribute to the tag to mark it for translation.
添加`i18n`属性到该标签上,把它标记为需要翻译的文本。
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-attribute" title="src/app/app.component.html" linenums="false">
@ -135,10 +250,15 @@ Add the `i18n` attribute to the tag to mark it for translation.
### Help the translator with a _description_ and _intent_
### 用描述和意图来帮助翻译人员
In order to translate it accurately, the translator may
need a description of the message.
Assign a description to the i18n attribute:
翻译人员可能需要待翻译文本的描述才能翻译准确。
为i18n属性添加描述
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-attribute-desc" title="src/app/app.component.html" linenums="false">
@ -152,6 +272,9 @@ within _this particular_ application context.
In front of the description, add some contextual meaning to the assigned string,
separating it from the description with the `|` character (`<meaning>|<description>`):
为了给出正确的翻译,翻译者需要知道你这段文本在特定情境下的 *真实意图*
在描述的前面,为指定的字符串添加一些上下文含义,用`|`将其与描述文字隔开(`<意图>|<描述>`)。
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-attribute-meaning" title="src/app/app.component.html" linenums="false">
@ -164,6 +287,10 @@ a message with *a variety of possible meanings* could have different translation
The Angular extraction tool preserves both the _meaning_ and the _description_ in the translation source file
to facilitiate contextually-specific translations.
如果所有地方出现的文本具有**相同**含义时,它们应该有**相同**的翻译,
但是如果在某些地方它具有**不同含义**,那么它应该有不同的翻译。
Angular的提取工具在翻译源文件中保留**含义**和**描述**,以支持符合特定上下文的翻译。
{@a no-element}
@ -315,12 +442,19 @@ The message maps those values to the appropriate translation:
## Create a translation source file with the _ng-xi18n_ tool
Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
## 使用_ng-xi18n_工具创建翻译源文件
Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
into a translation source file in an industry standard format.
使用`ng-xi18n`提取工具来将`i18n`标记的文本提取到一个符合行业标准格式的翻译源文件。
This is an Angular CLI tool in the `@angular/compiler-cli` npm package.
If you haven't already installed the CLI and its `platform-server` peer dependency, do so now:
它是在`@angular/compiler-cli` npm包中的一个Angular CLI工具。
如果你还没有安装这个CLI和它的 `platform-server`,安装它们:
<code-example language="sh" class="code-shell">
npm install @angular/compiler-cli @angular/platform-server --save
@ -331,6 +465,8 @@ If you haven't already installed the CLI and its `platform-server` peer dependen
Open a terminal window at the root of the application project and enter the `ng-xi18n` command:
在应用的项目根目录打开一个终端窗口,并输入`ng-xi18n`命令:
<code-example language="sh" class="code-shell">
./node_modules/.bin/ng-xi18n
@ -353,6 +489,8 @@ Windows users may have to quote the command like this: `"./node_modules/.bin/ng-
By default, the tool generates a translation file named **`messages.xlf`** in the
<a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">XML Localisation Interchange File Format (XLIFF, version 1.2)</a>.
工具默认生成一个名为**`messages.xlf`**的翻译文件,格式为<a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">XML本土化互换文件格式(XLIFF, version 1.2)</a>
{@a other-formats}
@ -429,24 +567,37 @@ It tells _npm_ to pass every flag thereafter to `ng-xi18n`.
## Translate text messages
The `ng-xi18n` command generates a translation source file
## 翻译文本信息
The `ng-xi18n` command generates a translation source file
in the project root folder named `messages.xlf`.
The next step is to translate the English language template
text into the specific language translation
files. The cookbook sample creates a Spanish translation file.
`ng-xi18n`命令在项目根目录生成一个名为`messages.xlf`的翻译源文件。
下一步是将英文模板文本翻译到目标语言的翻译文件。
本烹饪书创建了一个西班牙语翻译文件。
{@a localization-folder}
### Create a localization folder
### 新建一个本土化目录
You will probably translate into more than one other language so it's a good idea
for the project structure to reflect your entire internationalization effort.
One approach is to dedicate a folder to localization and store related assets,
你很有可能翻译到更多其他语言,所以为全部国际化工作做适当的调整项目目录结构是理所当然的。
One approach is to dedicate a folder to localization and store related assets ,
such as internationalization files, there.
其中一种方法是为本土化和相关资源(比如国际化文件)创建一个专门的目录。
<div class="l-sub-section">
@ -454,6 +605,8 @@ such as internationalization files, there.
Localization and internationalization are
<a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank">different but closely related terms</a>.
本土化和国际化是<a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank">不同但是很相近的概念</a>
</div>
@ -474,9 +627,14 @@ In the real world, you send the `messages.es.xlf` file to a Spanish translator w
using one of the
<a href="https://en.wikipedia.org/wiki/XLIFF#Editors" target="_blank">many XLIFF file editors</a>.
在现实世界中,`messages.es.xlf`文件会被发给西班牙语翻译,他们使用<a href="https://en.wikipedia.org/wiki/XLIFF#Editors" target="_blank">这些XLIFF文件编辑器</a>中的一种来翻译它。
This sample file is easy to translate without a special editor or knowledge of Spanish.
Open `messages.es.xlf` and find the first `<trans-unit>` section:
我们不需要任何编辑器或者西班牙语知识就可以轻易的翻译本例子文件。
打开`messages.es.xlf`并找到`<trans-unit>`节点:
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-hello" title="src/locale/messages.es.xlf (&lt;trans-unit&gt;)" linenums="false">
@ -484,11 +642,16 @@ Open `messages.es.xlf` and find the first `<trans-unit>` section:
This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute.
This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute.
这个XML元素代表了你使用`i18n`属性标记的`<h1>`问候语标签的翻译。
Using the _source_, _description_, and _meaning_ elements to guide your translation,
replace the `<target/>` tag with the Spanish greeting:
翻译中利用_source_、_description_和_meaning_元素的信息替换`<target/>`标签为西班牙语问候语:
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-hello" title="src/locale/messages.es.xlf (&lt;trans-unit&gt;, after translation)" linenums="false">
</code-example>
@ -502,6 +665,11 @@ replace the `<target/>` tag with the Spanish greeting:
Note that the tool generates the `id`. **Don't touch it.**
Its value depends on the content of the message and its assigned meaning.
Change either factor and the `id` changes as well.
注意`id`是工具生成的。不要修改它。
它的值取决于两个因素:信息的内容和其指定的含义。
改变任何一个因素,`id`就会改变。
See the **[translation file maintenance discussion](guide/i18n#maintenance)**.
@ -608,8 +776,12 @@ time to incorporate that translation into the application.
### The app before translation
### 翻译前的应用程序
When the previous steps finish, the sample app _and_ its translation file are as follows:
如下所示,是完成前面的步骤后的例子应用**和**它的翻译文件:
<code-tabs>
@ -643,24 +815,47 @@ When the previous steps finish, the sample app _and_ its translation file are as
## Merge the completed translation file into the app
## 合并已经翻译的文件
To merge the translated text into component templates,
compile the application with the completed translation file.
The process is the same whether the file is in `.xlf` format or
in another format that Angular understands, such as `.xlif` or `.xtb`.
The process is the same whether the file is in `.xlf` format or
in another format that Angular understands, such as `.xlif` or `.xtb`.
要合并已经翻译的文件到组件模板,使用翻译过的文件编译应用。
不管文件是`.xlf`格式还是其他Angular接受的格式`.xlif`和`.xtb`),流程是一样的。
You provide the Angular compiler with three new pieces of information:
你要为Angular编译器提供下列三种新信息
* The translation file.
翻译文件
* The translation file format.
翻译文件的格式
* The <a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">_Locale ID_</a>
(`es` or `en-US` for instance).
目标<a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">_语言环境ID_</a>
(例如`es`或`en-US`)
_How_ you provide this information depends upon whether you compile with
the JIT (_Just-in-Time_) compiler or the AOT (_Ahead-of-Time_) compiler.
the JIT (_Just-in-Time_) compiler or the AOT (_Ahead-of-Time_) compiler.
你如何提供这些信息取决于你使用的是JiT即时编译器还是AoT预先编译器。
* With [JIT](guide/i18n#jit), you provide the information at bootstrap time.
使用[JiT](guide/i18n#jit)时,在引导时提供
* With [AOT](guide/i18n#aot), you pass the information as `ngc` options.
使用[AoT](guide/i18n#aot)时,在`ngc`命令的选项里提供
{@a jit}
@ -668,16 +863,35 @@ the JIT (_Just-in-Time_) compiler or the AOT (_Ahead-of-Time_) compiler.
### Merge with the JIT compiler
### 用JiT编译器合并
The JIT compiler compiles the application in the browser as the application loads.
Translation with the JIT compiler is a dynamic process of:
1. Determining the language version for the current user.
JiT即时编译器在应用程序加载时在浏览器中编译应用。
在使用JiT编译器的环境中翻译是一个动态的流程包括
1. Determining the language version for the current user。
决定当前用户的语言,
2. Importing the appropriate language translation file as a string constant.
3. Creating corresponding translation providers to guide the JIT compiler.
导入合适的语言翻译文件到一个字符串常量,
3. Creating corresponding translation providers to guide the JiT compiler.
新建对应的翻译提供商来指导JiT编译器
4. Bootstrapping the application with those providers.
使用这些提供商来启动应用。
Open `index.html` and revise the launch script as follows:
打开`index.html`并这样修改加载脚本:
<code-example path="cb-i18n/src/index.html" region="i18n" title="index.html (launch script)" linenums="false">
</code-example>
@ -687,20 +901,32 @@ Open `index.html` and revise the launch script as follows:
In this sample, the user's language is hardcoded as a global `document.locale` variable
in the `index.html`.
在本例中,用户的语言在`index.html`中被硬编码到一个全局的`document.locale`变量中。
{@a text-plugin}
### SystemJS text plugin
### SystemJS文本插件
Notice the SystemJS mapping of `text` to a `systemjs-text-plugin.js`.
With the help of a text plugin, SystemJS can read any file as raw text and
return the contents as a string.
You'll need it to import the language translation file.
注意SystemJS将`text`映射为`systemjs-text-plug.js`。
在这个文本插件的帮助下SystemJS可以读取任何原始文件并将其内容作为字符串返回。
你需要使用它来导入语言翻译文件。
SystemJS doesn't ship with a raw text plugin but it's easy to add.
Create the following `systemjs-text-plugin.js` in the `src/` folder:
SystemJS没有自带原始文本插件但是我们很容易添加它。
在`src/`目录新建下面的`systemjs-text-plugin.js`文件:
<code-example path="cb-i18n/src/systemjs-text-plugin.js" title="src/systemjs-text-plugin.js" linenums="false">
</code-example>
@ -712,17 +938,32 @@ Create the following `systemjs-text-plugin.js` in the `src/` folder:
### Create translation providers
### 新建翻译提供商
Three providers tell the JIT compiler how to translate the template texts for a particular language
while compiling the application:
三种提供商帮助JiT编译在编译应用时将模板文本翻译到某种语言
* `TRANSLATIONS` is a string containing the content of the translation file.
`TRANSLATIONS`是含有翻译文件内容的字符串。
* `TRANSLATIONS_FORMAT` is the format of the file: `xlf`, `xlif`, or `xtb`.
`TRANSLATIONS_FORMAT`是文件的格式: `xlf`、`xlif`或`xtb`。
* `LOCALE_ID` is the locale of the target language.
`LOCALE_ID`是目标语言的语言环境。
The `getTranslationProviders()` function in the following `src/app/i18n-providers.ts`
creates those providers based on the user's _locale_
and the corresponding translation file:
在下面的`src/app/i18n-providers.ts`文件的`getTranslationProviders()`函数中,根据用户的**语言环境**和对应的翻译文件构建这些提供商:
<code-example path="cb-i18n/src/app/i18n-providers.ts" title="src/app/i18n-providers.ts">
</code-example>
@ -731,32 +972,54 @@ and the corresponding translation file:
1. It gets the locale from the global `document.locale` variable that was set in `index.html`.
它从在`index.html`中设置的全局`document.locale`变量中获取语言环境。
1. If there is no locale or the language is U.S. English (`en-US`), there is no need to translate.
The function returns an empty `noProviders` array as a `Promise`.
It must return a `Promise` because this function could read a translation file asynchronously from the server.
如果没有语言环境或者语言是美国英语(`en-US`),则就无需翻译。该函数以`Promise`的形式返回一个空的`noProviders`数组。它必须要返回`Promise`,因为这个函数可能异步从服务器读取翻译文件。
1. It creates a transaction filename from the locale according to the name and location convention
[described earlier](guide/i18n#localization-folder).
根据[上面描述](guide/i18n#localization-folder)的名字和本土化的约定,它根据语言环境创建一个合约文件名。
1. The `getTranslationsWithSystemJs()` method reads the translation and returns the contents as a string.
Notice that it appends `!text` to the filename, telling SystemJS to use the [text plugin](guide/i18n#text-plugin).
`getTranslationsWithSystemJs()`方法读取翻译并以字符串的形式返回其内容。
注意它在文件名上附加`!text`告诉SystemJS使用[文本插件](guide/i18n#text-plugin)。
1. The callback composes a providers array with the three translation providers.
回调函数使用这三种翻译提供商创建一个提供商数组。
1. Finally, `getTranslationProviders()` returns the entire effort as a promise.
最后,`getTranslationProviders()`返回以承诺的形式返回全部流程的结果。
{@a bootstrap-the-app}
### Bootstrap the app with translation providers
### 使用翻译提供商引导应用
The Angular `bootstrapModule()` method has a second _options_ parameter
that can influence the behavior of the compiler.
Angular的`bootstrapModule()`方法接受**可选的**第二参数,它可以影响编译器的行为。
You'll create an _options_ object with the translation providers from `getTranslationProviders()`
and pass it to `bootstrapModule`.
Open the `src/main.ts` and modify the bootstrap code as follows:
从`getTranslationProviders()`返回的翻译提供商创建_options_对象并将其传给`bootstrapModule`。
打开`src/main.ts`并这样修改引导代码:
<code-example path="cb-i18n/src/main.ts" title="src/main.ts" linenums="false">
</code-example>
@ -766,9 +1029,13 @@ Open the `src/main.ts` and modify the bootstrap code as follows:
Notice that it waits for the `getTranslationProviders()` promise to resolve before
bootstrapping the app.
注意,它等待`getTranslationProviders()`承诺的解析完成后,才引导应用。
The app is now _internationalized_ for English and Spanish and there is a clear path for adding
more languages.
现在,应用已经被国际化为英语版和西班牙语版,而且我们有了清晰的添加更多语言的方法。
{@a aot}
@ -776,10 +1043,15 @@ more languages.
### _Internationalization_ with the AOT compiler
### 使用AoT编译器时的国际化
The JIT compiler translates the application into the target language
while compiling dynamically in the browser.
That's flexible but may not be fast enough for your users.
JiT编译器在浏览器中动态编译应用时将其翻译到目标语言。
这样很灵活,但是对用户来讲,可能速度太慢。
The AOT (_Ahead-of-Time_) compiler is part of a build process that
produces a small, fast, ready-to-run application package.
When you internationalize with the AOT compiler, you pre-build
@ -788,27 +1060,52 @@ language. Then in the host web page, in this case `index.html`,
you determine which language the user needs
and serve the appropriate application package.
AoT预先编译器是一种构建流程出产尺寸小、速度快和可执行的应用程序包。
在使用Aot编译器的环境中国际化你为每种语言预先构建一个单独的应用程序包。然后在宿主网络页面`index.html`)中,你再决定用户需要哪种语言,并选择合适的应用程序包。
This cookbook doesn't cover how to build multiple application packages and
serve them according to the user's language preference.
It does explain the few steps necessary to tell the AOT compiler to apply a translations file.
本烹饪书不介绍如何构建多种应用程序包和如何根据用户的语言设置推送它们。
它介绍了一些必要的步骤来告诉AoT采用翻译文件。
Internationalization with the AOT compiler requires
some setup specifically for AOT compilation.
Start with the application project as shown
[just before merging the translation file](guide/i18n#app-pre-translation)
and refer to the [AOT cookbook](guide/aot-compiler) to make it _AOT-ready_.
使用AoT编译器时的国际化需要一些针对AoT的设置。
以[合并翻译文件之前](guide/i18n#app-pre-translation)的应用项目开始,并参见[AoT烹饪书](guide/aot-compiler)把它变成与AoT兼容的项目。
Next, issue an `ngc` compile command for each supported language, including English.
The result is a separate version of the application for each language.
接下来,为每种支持的语言(包括英语)运行一次`ngc`编译命令。
结果是每种语言都有自己单独的应用版本。
Tell AOT how to translate by adding three options to the `ngc` command:
通过添加下面三种选项到`ngc`命令来告诉AoT编译器如何翻译
* `--i18nFile`: the path to the translation file.
`--i18nFile`: 翻译文件的路径
* `--locale`: the name of the locale.
`--locale`: 语言环境的名字
* `--i18nFormat`: the format of the localization file.
`--i18nFormat`: 翻译文件的格式
For this sample, the Spanish language command would be:
本西班牙语例子的命令为:
<code-example language="sh" class="code-shell">
./node_modules/.bin/ngc --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf
@ -822,6 +1119,8 @@ For this sample, the Spanish language command would be:
Windows users may have to quote the command:
Windows用户可能需要双引号这个命令
<code-example language="sh" class="code-shell">
"./node_modules/.bin/ngc" --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf

View File

@ -1,8 +1,8 @@
@title
Documentation Overview
文档概览
@intro
How to read and use this documentation.
如何阅读本文档
@description
@ -11,11 +11,18 @@ How to read and use this documentation.
This page describes the Angular documentation at a high level.
If you're new to Angular, you may want to visit "[Learning Angular](guide/learning-angular)" first.
本页是 Angular 文档的概述。
如果你刚接触 Angular请先访问“[学习 Angular](guide/learning-angular)”。
## Themes
## 主题
The documentation is divided into major thematic sections, each
a collection of pages devoted to that theme.
本文档分成几大主题区,每个区包含一组围绕自己主题的页面。
<table width="100%">
@ -31,14 +38,25 @@ a collection of pages devoted to that theme.
<tr style=top>
<td>
<b><a href="../quickstart.html">QuickStart</a></b>
<p>
<b><a href="../quickstart.html">QuickStart</a></b>
</p>
<p>
<b><a href="../quickstart.html">快速起步</a></b>
</p>
</td>
<td>
A first taste of Angular<span if-docs="ts"> with zero installation.
A first taste of Angular<span if-docs="ts"> with zero installation.
Run "Hello World" in an online code editor and start playing with live code</span>.
零配置第一次尝试 Angular 在在线代码编辑器中运行 “Hello World”并利用在线代码开始体验。
</td>
</tr>
@ -46,7 +64,15 @@ a collection of pages devoted to that theme.
<tr style=top>
<td>
<b>Guide</b>
<p>
<b><a href="./">Guide</a></b>
</p>
<p>
<b><a href="./">指南</a></b>
</p>
</td>
<td>
@ -55,6 +81,10 @@ a collection of pages devoted to that theme.
Learn the Angular basics (you're already here!) like the setup for local development,
displaying data and accepting user input, injecting application services into components,
and building simple forms.
学习 Angular 基础知识(你已经在这儿了!),比如搭建本地开发环境、
显示数据和接受用户输入、注入应用程序服务到组件中,
以及构建简单表单。
</td>
</tr>
@ -62,13 +92,23 @@ a collection of pages devoted to that theme.
<tr style=top>
<td>
<b><a href="../api/">API Reference</a></b>
<p>
<b><a href="../api/">API Reference</a></b>
</p>
<p>
<b><a href="../api/">API参考手册</a></b>
</p>
</td>
<td>
Authoritative details about each of the Angular libraries.
关于 Angular 库中每一个成员的详尽、权威的资料。
</td>
</tr>
@ -76,7 +116,15 @@ a collection of pages devoted to that theme.
<tr style=top>
<td>
<b><a href="../tutorial/">Tutorial</a></b>
<p>
<b><a href="../tutorial/">Tutorial</a></b>
</p>
<p>
<b><a href="../tutorial/">教程</a></b>
</p>
</td>
<td>
@ -84,6 +132,9 @@ a collection of pages devoted to that theme.
A step-by-step, immersive approach to learning Angular that
introduces the major features of Angular in an application context.
按部就班、沉浸式的 Angular 学习之旅,在应用场景中介绍了 Angular 的各个主要特性。
</td>
</tr>
@ -91,13 +142,24 @@ a collection of pages devoted to that theme.
<tr style=top>
<td>
<b><a href=" ">Advanced</a></b>
<p>
<b><a href=" ">Advanced</a></b>
</p>
<p>
<b><a href=" ">高级</a></b>
</p>
</td>
<td>
In-depth analysis of Angular features and development practices.
深入分析 Angular 的特性和开发实践。
</td>
</tr>
@ -105,7 +167,15 @@ a collection of pages devoted to that theme.
<tr style=top if-docs="ts">
<td>
<b><a href="../cookbook/">Cookbook</a></b>
<p>
<b><a href="../cookbook/">Cookbook</a></b>
</p>
<p>
<b><a href="../cookbook/">烹饪宝典</a></b>
</p>
</td>
<td>
@ -113,6 +183,8 @@ a collection of pages devoted to that theme.
Recipes for specific application challenges, mostly code snippets with a minimum of exposition.
一组解决实际应用中某些特定挑战的“菜谱”,大部分是代码片段随带少量的详细阐述。
</td>
</tr>
@ -125,30 +197,70 @@ A few early pages are written as tutorials and are clearly marked as such.
The rest of the pages highlight key points in code rather than explain each step necessary to build the sample.
You can always get the full source through the #{_liveLink}s.
少量早期页面是作为教程来写的,并被清晰的标注出来。
其它页面的目的是展示代码中的关键点,而不是解释构建这个范例所需的每一个步骤。
可以通过在线例子的链接找到完整的源代码。
## Code samples
## 代码例子
Each page includes code snippets from a sample application that accompanies the page.
You can reuse these snippets in your applications.
每章都包含了该章附带例子应用的代码片段。可以在你的应用中重用这些代码片段。
Look for a link to a running version of that sample, often near the top of the page,
such as this <live-example nodownload name="architecture"></live-example> from the [Architecture](guide/architecture) page.
<span if-docs="ts">
The link launches a browser-based, code editor where you can inspect, modify, save, and download the code.
</span>
查找这些例子在线版本的链接,通常在页面的开头部分附近。
例如[架构](guide/architecture)章的<live-example name="architecture"></live-example>
<span if-docs="ts">
该链接启动浏览器代码编辑器,你可以查看、修改、保存和下载代码。
</span>
Alternatively, you can run the example locally, next to those `live-example` links you have a <a href="/resources/zips/architecture/architecture.zip">download link</a>.
Just download, unzip, run `npm install` to install the dependencies and run it with `npm start`.
不过,你也可以在本地运行这个例子,你可以点击<a href="/resources/zips/architecture/architecture.zip">下载链接</a>来下载。
只要下载、unzip、运行`npm install`安装依赖,然后运行`npm start`就可以了。
## Reference pages
## 参考资料
* The [Cheat Sheet](guide/cheatsheet) lists Angular syntax for common scenarios.
[速查表](guide/cheatsheet)列出了 Angular 在常见场景下的语法。
* The [Glossary](guide/glossary) defines terms that Angular developers should know.
<li if-docs="ts">The [Change Log](guide/change-log) announces what's new and changed in the documentation.</li>
[词汇表](guide/glossary)定义了 Angular 开发人员需要知道的术语。
* The [Change Log](guide/change-log) announces what's new and changed in the documentation.
[变更日志](guide/change-log)包含了文档新增与更新内容。
* The [API Reference](api/) is the authority on every public-facing member of the Angular libraries.
[API 参考手册](api/)是关于 Angular 库中每一个公共成员的权威参考资料。
## Feedback
We welcome feedback!
## 反馈
We welcome feedback!
我们期待您的反馈!
* Use the <a href="!{_ngDocRepoURL}" target="_blank" title="angular docs on github">!{_angular_io} Github repository</a> for **documentation** issues and pull requests.
* Use the <a href="!{_ngRepoURL}" target="_blank" title="angular source on github">Angular Github repository</a> to report issues with **Angular** itself.
<a href="!{_ngDocRepoURL}" target="_blank" title="angular docs on github">!{_angular_io} Github 库</a>提交**文档**相关的 issues 和 pull requests。
* Use the <a href="!{_ngRepoURL}" target="_blank" title="angular source on github">Angular Github repository</a> to report issues with **Angular** itself.
<a href="!{_ngRepoURL}" target="_blank" title="angular source on github">Angular Github 库</a>报告与 **Angular 本身**相关的 issues。

View File

@ -1,8 +1,8 @@
@title
Learning Angular
学习 Angular
@intro
A suggested path through the documentation for Angular newcomers.
Angular 初学者的推荐学习路径
@description
@ -19,31 +19,61 @@ You don't have to read the documentation straight through. Most pages stand on
Those new to Angular may wish to follow this popular learning path.
<br class="l-clear-left">
每个人的学习方式不一样。
你不一定要通读文档。本站大部分页面都是独立的。
Angular 初学者可能希望跟随下面的常见学习路径。
<br class="l-clear-left">
1. [Setup](guide/setup "Setup locally withe Quickstart seed") for local Angular development, if you haven't already done so.
1. Take the [*Tour of Heroes* tutorial](tutorial "Tour of Heroes").
如果你还没有这样做,[搭建](guide/setup "利用《快速起步》种子搭建本地开发环境")本地 Angular 开发环境。
1. Take the [*Tour of Heroes* tutorial](tutorial "Tour of Heroes").
学习[*英雄指南*教程](tutorial "Tour of Heroes")。
The *Tour of Heroes* takes you step-by-step from [setup](guide/setup)
to a full-featured example that demonstrates the essential characteristics of a professional application:
a sensible project structure, data binding, master/detail, services, dependency injection, navigation, and remote data access.
*英雄指南*从[搭建本地开发环境](guide/setup)开始,
带着你一步一步地开发出具有完整特征的示例程序,它包含了专业应用最基本的特征:
合理的项目结构、数据绑定、主从视图、服务、依赖注入、导航和远程数据访问等。
1. {@a architecture}Read the [Architecture](guide/architecture) overview for the big picture.
{@a architecture}阅读[架构](guide/architecture)了解大局。
1. [The Root Module](guide/appmodule) introduces the `NgModule` class that tells Angular how to compile and run your application.
[根模块](guide/appmodule)介绍了`NgModule`类,它为 Angular 描述如何编译和运行应用。
1. [Displaying Data](guide/displaying-data) shows how data binding puts component property values on screen.
[显示数据](guide/displaying-data)展示了数据绑定如何将组件属性值显示到屏幕上。
1. [User Input](guide/user-input) explains how to respond to user-initiated DOM events.
[用户输入](guide/user-input)解释了如何处理用户触发的 DOM 事件。
1. [Forms](guide/forms) covers data entry and validation within the UI.
[表单](guide/forms)涵盖了用户界面中数据的输入和验证。
1. [Dependency Injection](guide/dependency-injection) is the way to build large, maintainable applications
from small, single-purpose parts.
[依赖注入](guide/dependency-injection)是用小型和单用途部件来构建大型、易维护应用的途径。
1. [Template Syntax](guide/template-syntax) is a comprehensive study of Angular template HTML.
[模板语法](guide/template-syntax)对 Angular 模板 HTML 进行了完整的阐述。
After reading the above sections, feel free to skip around among the other pages on this site.
阅读以上内容后,你可以随意挑选在本站的其他页面进行阅读。
<div class="l-sub-section">
@ -51,8 +81,12 @@ After reading the above sections, feel free to skip around among the other pages
### Next Step
### 下一步
Try the [tutorial](tutorial "Tour of Heroes") if you're ready to start coding or
visit the [Architecture](guide/architecture "Basic Concepts") page if you prefer to learn the basic concepts first.
如果你想开始编程,那就试试[教程](tutorial "英雄指南")。或者如果你想要先学习基本概念,那么阅读[架构](guide/architecture "基本概念")页面。
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
@title
Npm Packages
npm 包
@intro
Recommended npm packages, and how to specify package dependencies.
推荐的 npm 包以及如何指定所依赖的包
@description
@ -10,23 +10,36 @@ Recommended npm packages, and how to specify package dependencies.
Angular applications and Angular itself depend upon features and functionality provided by a variety of third-party packages.
These packages are maintained and installed with the Node Package Manager (<a href="https://docs.npmjs.com/" target="_blank">npm</a>).
Angular应用程序以及Angular本身都依赖于很多第三方包(包括Angular自己)提供的特性和功能。
这些包由Node包管理器(<a href="https://docs.npmjs.com/" target="_blank">npm</a>)负责安装和维护。
<div class="l-sub-section">
Node.js and npm are essential to Angular development.
Node.js and npm are essential to Angular development.
Node.js和npm是做Angular开发的基础。
<a href="https://docs.npmjs.com/getting-started/installing-node" target="_blank" title="Installing Node.js and updating npm">
Get them now</a> if they're not already installed on your machine.
如果你的电脑上还没有装过,请<a href="https://docs.npmjs.com/getting-started/installing-node" target="_blank" title="Installing Node.js and updating npm">立即获取它</a>
**Verify that you are running node `v4.x.x` or higher and npm `3.x.x` or higher**
by running the commands `node -v` and `npm -v` in a terminal/console window.
Older versions produce errors.
通过在终端/控制台窗口中运行`node -v`和`npm -v`命令,来**验证下你是否正在使用node `v4.x.x`和npm `3.x.x`**。
过老的版本有可能出现问题。
Consider using [nvm](https://github.com/creationix/nvm) for managing multiple
versions of node and npm. You may need [nvm](https://github.com/creationix/nvm) if
you already have projects running on your machine that use other versions of node and npm.
我们建议使用[nvm](https://github.com/creationix/nvm)来管理node和npm的多个版本。如果你机器上已经有某些项目运行了node和npm的其它版本你就会需要[nvm](https://github.com/creationix/nvm)了。
</div>
@ -36,9 +49,14 @@ During [Setup](guide/setup), a <a href="https://docs.npmjs.com/files/package.jso
file is installed with a comprehensive starter set of
packages as specified in the `dependencies` and `devDependencies` sections.
我们在[搭建本地开发环境](guide/setup)一章中安装并解释了<a href="https://docs.npmjs.com/files/package.json" target="_blank">package.json</a>文件的
`dependencies`和`devDependencies`区中指定了一组适用于新手的综合依赖包。
You can use other packages but the packages in _this particular set_ work well together and include
everything you need to build and run the sample applications in this series.
你当然可以使用其它包,不过这一组可以很好的协同工作,而且包含了我们在个系列文档中构建和运行范例应用时所需的一切。
<div class="l-sub-section">
@ -46,6 +64,9 @@ everything you need to build and run the sample applications in this series.
Note: A cookbook or guide page may require an additional library such as *jQuery*.
注意:烹饪宝典或开发指南中的页面可能需要其它库,比如*jQuery*。
</div>
@ -54,18 +75,34 @@ You'll install more than you need for the QuickStart guide.
No worries!
You only serve to the client those packages that the application actually requests.
它们远远超过了我们将在“快速起步”中所需要用到的。
实际上,它比我们在大多数应用中需要的还多。
安装的包比我们实际需要的包多,其实并没有什么坏处。
我们最终只会往客户端发送程序中实际用到的那些包。
This page explains what each package does. You can make substitutions later to suit your tastes and experience.
本页面会解释每一个包是干什么的,以后你就可以根据自己的喜好和经验,随意替换它们了。
## *dependencies* and *devDependencies*
## *dependencies*和*devDependencies*
The `package.json` includes two sets of packages,
[dependencies](guide/npm-packages#dependencies) and [devDependencies](guide/npm-packages#dev-dependencies).
The *dependencies* are essential to *running* the application.
The *devDependencies* are only necessary to *develop* the application.
`package.json`包含两组包:[dependencies](guide/npm-packages#dependencies)和[devDependencies](guide/npm-packages#dev-dependencies)。
The *dependencies* are essential to *running* the application.
The *devDependencies* are only necessary to *develop* the application.
You can exclude them from production installations by adding `--production` to the install command, as follows:
*dependencies*下的这些包是*运行*本应用的基础,而*devDependencies*下的只在*开发*此应用时才用得到。
通过为`install`命令添加`--production`参数,你在产品环境下安装时排除*devDependencies*下的包,就像这样:
<code-example format="." language="bash">
npm install my-application --production
@ -80,48 +117,82 @@ You can exclude them from production installations by adding `--production` to t
## *dependencies*
The `dependencies` section of `package.json` contains:
* ***Features***: Feature packages give the application framework and utility capabilities.
应用程序的`package.json`文件中,`dependencies`区下有三类包:
* ***Polyfills***: Polyfills plug gaps in the browser's JavaScript implementation.
* ***Features*** : Feature packages give the application framework and utility capabilities.
* ***Other***: Other libraries that support the application such as `bootstrap` for HTML widgets and styling.
**特性*** - 特性包为应用程序提供了框架和工具方面的能力。
* ***Polyfills*** : Polyfills plug gaps in the browser's JavaScript implementation.
***填充(Polyfills)*** - 填充包弥合了不同浏览器上的JavaScript实现方面的差异。
* ***Other*** : Other libraries that support the application such as `bootstrap` for HTML widgets and styling.
***其它*** - 其它库对本应用提供支持,比如`bootstrap`包提供了HTML中的小部件和样式。
### Feature Packages
***@angular/core***: Critical runtime parts of the framework needed by every application.
### 特性包
***@angular/core*** : Critical runtime parts of the framework needed by every application.
Includes all metadata decorators, `Component`, `Directive`, dependency injection, and the component lifecycle hooks.
***@angular/common***: The commonly needed services, pipes, and directives provided by the Angular team.
***@angular/core*** - 框架中关键的运行期部件,每一个应用都需要它。
包括所有的元数据装饰器:`Component`、`Directive`,依赖注入系统,以及组件生命周期钩子。
***@angular/compiler***: Angular's *Template Compiler*.
It understands templates and can convert them to code that makes the application run and render.
***@angular/common*** : The commonly needed services, pipes, and directives provided by the Angular team.
***@angular/common*** - 常用的那些由Angular开发组提供的服务、管道和指令。
***@angular/compiler*** : Angular's *Template Compiler*.
It understands templates and can convert them to code that makes the application run and render.
Typically you dont interact with the compiler directly; rather, you use it indirectly via `platform-browser-dynamic` or the offline template compiler.
***@angular/platform-browser***: Everything DOM and browser related, especially
the pieces that help render into the DOM.
This package also includes the `bootstrapStatic()` method
for bootstrapping applications for production builds that pre-compile templates offline.
***@angular/compiler*** - Angular的*模板编译器*。
它会理解模板,并且把模板转化成代码,以供应用程序运行和渲染。
开发人员通常不会直接跟这个编译器打交道,而是通过`platform-browser-dynamic`或离线模板编译器间接使用它。
***@angular/platform-browser-dynamic***: Includes [Providers](api/core/index/Provider-type-alias)
and a [bootstrap](guide/ngmodule#bootstrap) method for applications that
***@angular/platform-browser*** : Everything DOM and browser related, especially the pieces that help render into theDOM.
This package also includes the `bootstrapStatic()` method for bootstrapping applications for production builds that pre-compile templates offline.
***@angular/platform-browser*** - 与DOM和浏览器相关的每样东西特别是帮助往DOM中渲染的那部分。
这个包还包含bootstrapStatic方法用来引导那些在产品构建时需要离线预编译模板的应用程序。
***@angular/platform-browser-dynamic*** : Includes [Providers](api/core/index/Provider-type-alias) and a [bootstrap](guide/ngmodule#bootstrap) method for applications that
compile templates on the client. Dont use offline compilation.
Use this package for bootstrapping during development and for bootstrapping plunker samples.
***@angular/http***: Angular's HTTP client.
***@angular/platform-browser-dynamic*** - 为应用程序提供一些[提供商](api/core/index/Provider-type-alias)和[bootstrap](guide/ngmodule#bootstrap)方法,以便在客户端编译模板。不要用于离线编译。
我们使用这个包在开发期间引导应用以及引导plunker中的范例。
***@angular/router***: Component router.
***@angular/http*** : Angular's HTTP client.
***@angular/upgrade***: Set of utilities for upgrading AngularJS applications to Angular.
***@angular/http*** - Angular的HTTP客户端。
***[system.js](https://github.com/systemjs/systemjs)***: A dynamic module loader compatible with the
***@angular/router*** : Component router.
***@angular/router*** - 路由器。
***@angular/upgrade*** : Set of utilities for upgrading AngularJS applications to Angular.
***@angular/upgrade*** - 一组用于升级AngularJS应用的工具。
***[system.js](https://github.com/systemjs/systemjs)*** : A dynamic module loader compatible with the
[ES2015 module](http://www.2ality.com/2014/09/es6-modules-final.html) specification.
Other viable choices include the well-regarded [webpack](https://webpack.github.io/).
***[system.js](https://github.com/systemjs/systemjs)*** - 是一个动态的模块加载器,
与[ES2015模块](http://www.2ality.com/2014/09/es6-modules-final.html)规范兼容。
还有很多其它选择,比如广受欢迎的[webpack](https://webpack.github.io/)。
SystemJS被用在了我们的文档范例中。因为它能工作。
Your future applications are likely to require additional packages that provide
HTML controls, themes, data access, and various utilities.
今后应用程序很可能还会需要更多的包比如HTML控件、主题、数据访问以及其它多种工具。
{@a polyfills}
@ -130,11 +201,18 @@ HTML controls, themes, data access, and various utilities.
### Polyfill packages
### 填充(Polyfill)包
Angular requires certain [polyfills](https://en.wikipedia.org/wiki/Polyfill) in the application environment.
Install these polyfills using the npm packages that Angular lists in the *peerDependencies* section of its `package.json`.
在应用程序的运行环境中Angular需要某些[填充库](https://en.wikipedia.org/wiki/Polyfill)。
我们通过特定的npm包来安装这些填充库Angular本身把它列在了`package.json`中的*peerDependencies*区。
You must list these packages in the `dependencies` section of your own `package.json`.
但我们必须把它列在我们`package.json`文件的`dependencies`区。
<div class="l-sub-section">
@ -142,6 +220,9 @@ You must list these packages in the `dependencies` section of your own `package.
For background on this requirement, see [Why peerDependencies?](guide/npm-packages#why-peer-dependencies).
查看下面的“[为什么用peerDependencies?](guide/npm-packages#why-peer-dependencies)”,以了解这项需求的背景。
</div>
@ -149,17 +230,29 @@ For background on this requirement, see [Why peerDependencies?](guide/npm-packag
***core-js***: Patches the global context (window) with essential features of ES2015 (ES6).
You may substitute an alternative polyfill that provides the same core APIs.
When these APIs are implemented by the major browsers, this dependency will become unnecessary.
***core-js*** - 为全局上下文(window)打的补丁提供了ES2015(ES6)的很多基础特性。
我们也可以把它换成提供了相同内核API的其它填充库。
一旦所有的“主流浏览器”都实现了这些API这个依赖就可以去掉了。
***rxjs***: A polyfill for the [Observables specification](https://github.com/zenparsing/es-observable) currently before the
***rxjs*** : A polyfill for the [Observables specification](https://github.com/zenparsing/es-observable) currently before the
[TC39](http://www.ecma-international.org/memento/TC39.htm) committee that determines standards for the JavaScript language.
You can pick a preferred version of *rxjs* (within a compatible version range)
without waiting for Angular updates.
***zone.js***: A polyfill for the [Zone specification](https://gist.github.com/mhevery/63fdcdf7c65886051d55) currently before the
***rxjs*** - 一个为[可观察对象(Observable)规范](https://github.com/zenparsing/es-observable)提供的填充库,该规范已经提交给了
[TC39](http://www.ecma-international.org/memento/TC39.htm)委员会以决定是否要在JavaScript语言中进行标准化。
开发人员应该能在兼容的版本中选择一个喜欢的*rxjs*版本而不用等Angular升级。
***zone.js*** : A polyfill for the [Zone specification](https://gist.github.com/mhevery/63fdcdf7c65886051d55) currently before the
[TC39](http://www.ecma-international.org/memento/TC39.htm) committee that determines standards for the JavaScript language.
You can pick a preferred version of *zone.js* to use (within a compatible version range)
without waiting for Angular updates.
***zone.js*** - 一个为[Zone规范](https://gist.github.com/mhevery/63fdcdf7c65886051d55)提供的填充库,该规范已经提交给了
[TC39](http://www.ecma-international.org/memento/TC39.htm)委员会以决定是否要在JavaScript语言中进行标准化。
开发人员应该能在兼容的版本中选择一个喜欢的*zone.js*版本而不用等Angular升级。
{@a other}
@ -167,36 +260,58 @@ without waiting for Angular updates.
### Other helper libraries
***angular-in-memory-web-api***: An Angular-supported library that simulates a remote server's web api
without requiring an actual server or real HTTP calls.
### 其它辅助库
***angular-in-memory-web-api*** : An Angular-supported library that simulates a remote server's web api
without requiring an actual server or real HTTP calls.
Good for demos, samples, and early stage development (before you even have a server).
Read about it in the [HTTP Client](guide/server-communication#in-mem-web-api) page.
***bootstrap***: [Bootstrap](http://getbootstrap.com/) is a popular HTML and CSS framework for designing responsive web apps.
***angular-in-memory-web-api*** - 一个Angular的支持库它能模拟一个远端服务器的Web API而不需要依赖一个真实的服务器或发起真实的HTTP调用。
对演示、文档范例和开发的早期阶段(那时候我们可能还没有服务器呢)非常有用。
请到[Http客户端](guide/server-communication#appendix-tour-of-heroes-in-memory-server)一章中了解更多知识。
***bootstrap*** : [Bootstrap](http://getbootstrap.com/) is a popular HTML and CSS framework for designing responsive web apps.
Some of the samples improve their appearance with *bootstrap*.
***bootstrap*** - [bootstrap](http://getbootstrap.com/)是一个广受欢迎的HTML和CSS框架可用来设计响应式网络应用。
有些文档中的范例使用了*bootstrap*来强化它们的外观。
{@a dev-dependencies}
## *devDependencies*
The packages listed in the *devDependencies* section of the `package.json` help you develop the application.
You don't have to deploy them with the production application although there is no harm in doing so.
***[concurrently](https://www.npmjs.com/package/concurrently)***:
列在`package.json`文件中*devDependencies*区的包会帮助我们开发该应用程序。
我们不用把它们部署到产品环境的应用程序中 —— 虽然这样做也没什么坏处。
***[concurrently](https://www.npmjs.com/package/concurrently)*** :
A utility to run multiple *npm* commands concurrently on OS/X, Windows, and Linux operating systems.
***[lite-server](https://www.npmjs.com/package/lite-server)***:
A light-weight, static file server, by [John Papa](http://johnpapa.net/)
***[concurrently](https://www.npmjs.com/package/concurrently)*** - 一个用来在OS/X、Windows和Linux操作系统上同时运行多个*npm*命令的工具
***[lite-server](https://www.npmjs.com/package/lite-server)*** :
A light-weight, static file server, by [John Papa](http://johnpapa.net/)
with excellent support for Angular apps that use routing.
***[typescript](https://www.npmjs.com/package/typescript)***:
***[lite-server](https://www.npmjs.com/package/lite-server)*** - 一个轻量级、静态的服务器,
由[John Papa](http://johnpapa.net/)开发和维护。对使用到路由的Angular程序提供了很好的支持。
***[typescript](https://www.npmjs.com/package/typescript)*** :
the TypeScript language server, including the *tsc* TypeScript compiler.
***@types/\****: TypeScript definition files.
***[typescript](https://www.npmjs.com/package/typescript)*** - TypeScript语言的服务器包含了TypeScript编译器*tsc*。
***@types/\**** : TypeScript definition files.
Learn more about it in the [TypeScript Configuration](guide/typescript-configuration#typings) guide.
***@types/\**** - “TypeScript定义”文件管理器。
要了解更多,请参见[TypeScript配置](guide/typescript-configuration#typings)页。
{@a why-peer-dependencies}
@ -204,57 +319,110 @@ Learn more about it in the [TypeScript Configuration](guide/typescript-configura
## Why *peerDependencies*?
There isn't a [*peerDependencies*](https://nodejs.org/en/blog/npm/peer-dependencies/) section in the QuickStart `package.json`.
But Angular has a *peerDependencies* section in
*its* `package.json`, which has important consequences for your application.
## 为什么使用*peerDependencies*
This section explains why you load the [polyfill](guide/npm-packages#polyfills) *dependency*
packages in the QuickStart application's `package.json`,
There isn't a [*peerDependencies*](https://nodejs.org/en/blog/npm/peer-dependencies/) section in the QuickStart `package.json`.
But Angular has a *peerDependencies* section in
*its* `package.json`, which has important consequences for your application.
在“快速起步”的`package.json`文件中,并没有[*peerDependencies*](https://nodejs.org/en/blog/npm/peer-dependencies/)区。
但是Angular本身在*它自己的* `package.json` 中有,
它对我们的应用程序有重要的影响。
This section explains why you load the [polyfill](guide/npm-packages#polyfills) *dependency* packages in the QuickStart application's`package.json`,
and why you'll need those packages in your own applications.
它解释了为什么我们要在“快速起步”的`package.json`文件中加载这些[填充库(polyfill)](guide/npm-packages#polyfills)依赖包,
以及为什么我们在自己的应用中会需要它们。
An explanation of [peer dependencies](https://nodejs.org/en/blog/npm/peer-dependencies/) follows.
然后是对[平级依赖(peer dependencies)](https://nodejs.org/en/blog/npm/peer-dependencies/)的简短解释。
Packages depend on other packages. For example, your application depends on the Angular package.
Two packages, "A" and "B", could depend on the same third package "C".
每个包都依赖其它的包比如我们的应用程序就依赖于Angular包。
Two packages, "A" and "B", could depend on the same third package "C".
"A" and "B" might both list "C" among their *dependencies*.
What if "A" and "B" depend on different versions of "C" ("C1" and "C2"). The npm package system supports that.
两个包“A”和“B”可能依赖共同的第三个包“C”。
"A"和“B”可能都在它们的*dependencies*中列出了“C”。
What if "A" and "B" depend on different versions of "C" ("C1" and "C2"). The npm package system supports that.
It installs "C1" in the `node_modules` folder for "A" and "C2" in the `node_modules` folder for "B".
Now "A" and "B" have their own copies of "C" and they run without interferring with one another.
如果“A”和“B”依赖于“C”的不同版本("C1"和“C2”)。npm包管理系统也能支持
它会把“C1”安装到“A”的`node_modules`目录下给“A”用把“C2”安装到“B”的`node_modules`目录下给“B”用。
现在“A”和“B”都有了它们自己的一份“C”的复本它们运行起来也互不干扰。
But there is a problem. Package "A" may require the presence of "C1" without actually calling upon it directly.
"A" may only work if *everyone is using "C1"*. It falls down if any part of the application relies on "C2".
但是有一个问题。包“A”可能只需要“C1”出现就行而实际上并不会直接调用它。
"A"可能只有当*每个人都使用“C1”时*才能正常工作。如果程序中的任何一个部分依赖了“C2”它就会失败。
The solution is for "A" to declare that "C1" is a *peer dependency*.
要想解决这个问题“A”就需要把“C1”定义为它的*平级依赖*。
The difference between a `dependency` and a `peerDependency` is roughly this:
在`dependencies`和`peerDependencies`之间的区别大致是这样的:
>A **dependency** says, "I need this thing directly available to *me*."
>
>**dependency**说:“我需要这东西*对我*是直接可用的。”
>
>A **peerDependency** says, "If you want to use me, you need this thing available to *you*."
>
>**peerDependency**说:“如果你想使用我,你得先确保这东西*对你*是可用的”
The Angular `package.json` specifies several *peer dependency* packages,
The Angular `package.json` specifies several *peer dependency* packages,
each pinned to a particular version of a third-party package.
Angular就存在这个问题。
因此Angular的`package.json`中指定了一系列*平级依赖*包,
把每个第三方包都固定在一个特定的版本上。
### You must install Angular's *peerDependencies* yourself.
### 我们必须自己安装Angular的*peerDependencies*。
When *npm* installs packages listed in *your* `dependencies` section,
it also installs the packages listed within *their* packages `dependencies` sections.
The process is recursive.
当*npm*安装那些在*我们的*`dependencies`区指定的包时,
它也会同时安装上在*那些包*的`dependencies`区所指定的那些包。
这个安装过程是递归的。
However, as of version 3, *npm* does *not* install packages listed in *peerDependencies* sections.
但是在npm的第三版中*它不会*安装列在*peerDependencies*区的那些包。
This means that when your application installs Angular, ***npm* doesn't automatically install
the packages listed in Angular's *peerDependencies* section**.
这意味着当我们的应用程序安装Angular时***npm*将不会自动安装列在Angular的*peerDependencies*区的那些包**
Fortunately, *npm* issues a warning (a) When any *peer dependencies* are missing, or (b)
When the application or any of its other dependencies
installs a different version of a *peer dependency*.
installs a different version of a *peer dependency*.
幸运的是,*npm*会在下列情况下给我们警告:(a) 当任何*平级依赖*缺失时 或(b) 当应用程序或它的任何其它依赖安装了与*平级依赖*不同版本的包时。
These warnings guard against accidental failures due to version mismatches.
They leave you in control of package and version resolution.
这些警告可以避免因为版本不匹配而导致的意外错误。
它们让我们可以控制包和版本的解析过程。
It is your responsibility to list all *peer dependency* packages **among your own *devDependencies***.
我们的责任是,把所有*平级依赖*包都**列在我们自己的*devDependencies*中**。
<div class="l-sub-section">
@ -262,10 +430,18 @@ It is your responsibility to list all *peer dependency* packages **among your ow
#### The future of *peerDependencies*
#### *peerDependencies*的未来
The Angular polyfill dependencies are hard requirements. Currently, there is no way to make them optional.
However, there is an npm feature request for "optional peerDependencies," which would allow you to model this relationship better.
Angular的填充库依赖只是一个给开发人员的建议或提示以便它们知道Angular期望用什么。
它们不应该像现在一样是硬需求,但目前我们也不知道该如何把它们设置为可选的。
However, there is an npm feature request for "optional peerDependencies," which would allow you to model this relationship better.
When this feature request is implemented, Angular will switch from *peerDependencies* to *optionalPeerDependencies* for all polyfills.
不过有一个npm的新特性申请叫做“可选的peerDependencies”它将会允许我们更好的对这种关系建模。
一旦它被实现了Angular将把所有填充库从*peerDependencies*区切换到*optionalPeerDependencies*去。
</div>

View File

@ -1,8 +1,8 @@
@title
Pipes
管道
@intro
Pipes transform displayed values within a template.
管道可以在模板中转换显示的内容。
@description
@ -11,29 +11,48 @@ Pipes transform displayed values within a template.
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, you could push their raw `toString` values directly to the view,
but that rarely makes for a good user experience.
For example, in most use cases, users prefer to see a date in a simple format like
<samp>April 15, 1988</samp> rather than the 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 editing. You may notice that you
desire many of the same transformations repeatedly, both within and across many applications.
You can almost think of them as styles.
In fact, you might like to apply them in your HTML templates as you do styles.
显然,有些值最好显示成用户友好的格式。我们很快就会发现,在很多不同的应用中,都在重复做出某些相同的变换。
我们几乎会把它们看做某种CSS样式事实上我们也确实更喜欢在HTML模板中应用它们 —— 就像CSS样式一样。
Introducing Angular pipes, a way to write display-value transformations that you can declare in your HTML.
通过引入Angular管道我们可以把这种简单的“显示-值”转换器声明在HTML中。
You can run the <live-example></live-example> in Plunker and download the code from there.
试试<live-example>在线例子</live-example>
## Using pipes
## 使用管道
A pipe takes in data as input and transforms it to a desired output.
In this page, you'll use pipes to transform a component's birthday property into
a human-friendly date.
管道把数据作为输入,然后转换它,给出期望的输出。
我们将把组件的`birthday`属性转换成对人类更友好的日期格式,来说明这一点:
<code-example path="pipes/src/app/hero-birthday1.component.ts" title="src/app/hero-birthday1.component.ts" linenums="false">
@ -43,6 +62,8 @@ a human-friendly date.
Focus on the component's template.
重点看下组件的模板。
<code-example path="pipes/src/app/app.component.html" region="hero-birthday-template" title="src/app/app.component.html" linenums="false">
@ -54,6 +75,9 @@ Inside the interpolation expression, you flow the component's `birthday` value t
[pipe operator](guide/template-syntax#pipe) ( | ) to the [Date pipe](api/common/index/DatePipe-pipe)
function on the right. All pipes work this way.
在这个插值表达式中,我们让组件的`birthday`值通过[管道操作符](guide/template-syntax#pipe)( | )流动到
右侧的[Date管道](api/common/index/DatePipe-class)函数中。所有管道都会用这种方式工作。
<div class="l-sub-section">
@ -62,6 +86,8 @@ function on the right. All pipes work this way.
The `Date` and `Currency` pipes need the *ECMAScript Internationalization API*.
Safari and other older browsers don't support it. You can add support with a polyfill.
`Date`和`Currency`管道需要**ECMAScript国际化I18nAPI**但Safari和其它老式浏览器不支持它该问题可以用垫片Polyfill解决。
<code-example language="html">
&lt;script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"&gt;&lt;/script&gt;
@ -76,10 +102,16 @@ Safari and other older browsers don't support it. You can add support with a pol
## Built-in pipes
## 内置的管道
Angular comes with a stock of pipes such as
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
They are all available for use in any template.
Angular内置了一些管道比如`DatePipe`、`UpperCasePipe`、`LowerCasePipe`、`CurrencyPipe`和`PercentPipe`。
它们全都可以直接用在任何模板中。
<div class="l-sub-section">
@ -88,8 +120,12 @@ They are all available for use in any template.
Read more about these and many other built-in pipes in the [pipes topics](api/#!?query=pipe) of the
[API Reference](api); filter for entries that include the word "pipe".
要学习更多内置管道的知识,参见[API参考手册](api/#!?apiFilter=pipe)并用“pipe”为关键词对结果进行过滤。
Angular doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in the [Appendix](guide/pipes#no-filter-pipe) of this page.
Angular没有`FilterPipe`或`OrderByPipe`管道,原因在[后面的附录中](guide/pipes#no-filter-pipe)有解释。
</div>
@ -98,13 +134,22 @@ Angular doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in
## Parameterizing a pipe
## 对管道进行参数化
A pipe can accept any number of optional parameters to fine-tune its output.
To add parameters to a pipe, follow the pipe name with a colon ( : ) and then the parameter value
(such as `currency:'EUR'`). If the pipe accepts multiple parameters, separate the values with colons (such as `slice:1:5`)
管道可能接受任何数量的可选参数来对它的输出进行微调。
我们可以在管道名后面添加一个冒号( : )再跟一个参数值,来为管道添加参数(比如`currency:'EUR'`)。
如果我们的管道可以接受多个参数,那么就用冒号来分隔这些参数值(比如`slice:1:5`)。
Modify the birthday template to give the date pipe a format parameter.
After formatting the hero's April 15th birthday, it renders as **<samp>04/15/88</samp>**:
我们将通过修改生日模板来给这个日期管道提供一个格式化参数。
当格式化完该英雄的4月15日生日之后它应该被渲染成**<samp>04/15/88</samp>**。
<code-example path="pipes/src/app/app.component.html" region="format-birthday" title="src/app/app.component.html" linenums="false">
@ -118,9 +163,14 @@ The parameter value can be any valid template expression,
such as a string literal or a component property.
In other words, you can control the format through a binding the same way you control the birthday value through a binding.
参数值可以是任何有效的模板表达式(参见[模板语法](guide/template-syntax)中的[模板表达式](guide/template-syntax#template-expressions)部分),比如字符串字面量或组件的属性。
换句话说,借助属性绑定,我们也可以像用绑定来控制生日的值一样,控制生日的显示格式。
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`属性。这里是新组件的模板:
<code-example path="pipes/src/app/hero-birthday2.component.ts" region="template" title="src/app/hero-birthday2.component.ts (template)" linenums="false">
@ -132,6 +182,9 @@ You also added a button to the template and bound its click event to the compone
That method toggles the component's `format` property between a short form
(`'shortDate'`) and a longer form (`'fullDate'`).
我们还能在模板中添加一个按钮,并把它的点击事件绑定到组件的`toggleFormat()`方法。
此方法会在短日期格式(`'shortDate'`)和长日期格式(`'fullDate'`)之间切换组件的`format`属性。
<code-example path="pipes/src/app/hero-birthday2.component.ts" region="class" title="src/app/hero-birthday2.component.ts (class)" linenums="false">
@ -143,6 +196,8 @@ As you 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 class='image-display'>
<img src='assets/images/devguide/pipes/date-format-toggle-anim.gif' alt="Date Format Toggle"></img>
@ -157,18 +212,25 @@ As you click the button, the displayed date alternates between
Read more about the `DatePipe` format options in the [Date Pipe](api/common/index/DatePipe-pipe)
API Reference page.
要了解更多`DatePipes`的格式选项,请参阅[API文档](api/common/index/DatePipe-class)。
</div>
## Chaining pipes
## 链式管道
You can chain pipes together in potentially useful combinations.
In the following example, to display the birthday in uppercase,
the birthday is chained to the `DatePipe` and on to the `UpperCasePipe`.
The birthday displays as **<samp>APR 15, 1988</samp>**.
我们可以把管道链在一起,以组合出一些潜在的有用功能。
下面这个例子中,我们把`birthday`链到`DatePipe`管道,然后又链到`UpperCasePipe`,这样我们就可以把生日显示成大写形式了。
比如下面的代码就会把生日显示成**<samp>APR 15, 1988</samp>**
<code-example path="pipes/src/app/app.component.html" region="chained-birthday" title="src/app/app.component.html" linenums="false">
@ -179,6 +241,8 @@ The birthday displays as **<samp>APR 15, 1988</samp>**.
This example&mdash;which displays **<samp>FRIDAY, APRIL 15, 1988</samp>**&mdash;chains
the same pipes as above, but passes in a parameter to `date` as well.
下面这个显示**<samp>FRIDAY, APRIL 15, 1988</samp>**的例子用同样的方式链接了这两个管道,而且同时还给`date`管道传进去一个参数。
<code-example path="pipes/src/app/app.component.html" region="chained-parameter-birthday" title="src/app/app.component.html" linenums="false">
@ -189,9 +253,14 @@ the same pipes as above, but passes in a parameter to `date` as well.
## Custom pipes
## 自定义管道
You can write your own custom pipes.
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers:
我们还可以写自己的自定义管道。
下面就是一个名叫`ExponentialStrengthPipe`的管道,它可以放大英雄的能力:
<code-example path="pipes/src/app/exponential-strength.pipe.ts" title="src/app/exponential-strength.pipe.ts" linenums="false">
@ -201,17 +270,34 @@ Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's pow
This pipe definition reveals the following 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.
Your pipe has one such parameter: the `exponent`.
当每个输入值被传给`transform`方法时,还会带上另一个参数,比如我们这个管道中的`exponent`(放大指数)。
* To tell Angular that this is a pipe, you apply the
`@Pipe` decorator, which you import from the core Angular library.
我们通过`@Pipe`装饰器告诉Angular这是一个管道。该装饰器是从Angular的`core`库中引入的。
* The `@Pipe` decorator allows you to define the
pipe name that you'll use within template expressions. It must be a valid JavaScript identifier.
Your pipe's name is `exponentialStrength`.
这个`@Pipe`装饰器允许我们定义管道的名字这个名字会被用在模板表达式中。它必须是一个有效的JavaScript标识符。
比如,我们这个管道的名字是`exponentialStrength`。
<div class="l-sub-section">
@ -219,10 +305,16 @@ Your pipe has one such parameter: the `exponent`.
### 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.
Technically, it's optional; Angular looks for and executes the `transform` method regardless.
`transform`方法是管道的基本要素。
`PipeTransform`*接口*中定义了它,并用它指导各种工具和编译器。
理论上说它是可选的。Angular不会管它而是直接查找并执行`transform`方法。
</div>
@ -230,6 +322,9 @@ Technically, it's optional; Angular looks for and executes the `transform` metho
Now you need a component to demonstrate the pipe.
现在,我们需要一个组件来演示这个管道。
<code-example path="pipes/src/app/power-booster.component.ts" title="src/app/power-booster.component.ts" linenums="false">
</code-example>
@ -244,9 +339,16 @@ Now you need a component to demonstrate the pipe.
Note the following:
要注意的有两点:
* You use your custom pipe the same way you use built-in pipes.
我们使用自定义管道的方式和内置管道完全相同。
* You must include your pipe in the `declarations` array of the `AppModule`.
我们必须在`AppModule`的`declarations`数组中包含这个管道。
<div class="callout is-helpful">
@ -258,11 +360,20 @@ Note the following:
<header>
别忘了`declarations`数组
</header>
You must manually register custom pipes.
If you don't, Angular reports an error.
In the previous example, you didn't list the `DatePipe` because all
Angular built-in pipes are pre-registered.
我们必须手动注册自定义管道。如果忘了Angular就会报告一个错误。
在前一个例子中我们没有把`DatePipe`列进去这是因为Angular所有的内置管道都已经预注册过了。
</div>
@ -271,12 +382,19 @@ Angular built-in pipes are pre-registered.
To probe the behavior in the <live-example></live-example>,
change the value and optional exponent in the template.
如果我们试一下这个<live-example></live-example>,就可以通过修改值和模板中的可选部分来体会其行为。
## Power Boost Calculator
## 能力倍增计算器
It's not much fun updating the template to test the custom pipe.
Upgrade the example to a "Power Boost Calculator" that combines
your pipe and two-way data binding with `ngModel`.
仅仅升级模板来测试这个自定义管道其实没多大意思。
我们干脆把这个例子升级为“能力倍增计算器”,它可以把该管道和使用`ngModel`的双向数据绑定组合起来。
<code-example path="pipes/src/app/power-boost-calculator.component.ts" title="src/app/power-boost-calculator.component.ts">
@ -296,17 +414,28 @@ your pipe and two-way data binding with `ngModel`.
## Pipes and change detection
## 管道与变更检测
Angular looks for changes to data-bound values through a *change detection* process that runs after every DOM 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 you use a pipe.
当我们使用管道时Angular选用了一种简单、快速的变更检测算法。
### No pipe
### 无管道
In the next example, the component 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`数组中的每个英雄。下面是它的模板:
<code-example path="pipes/src/app/flying-heroes.component.html" region="template-1" title="src/app/flying-heroes.component.html (v1)" linenums="false">
@ -316,6 +445,9 @@ its display of every hero in the `heroes` array. Here's the template:
The companion component class provides heroes, adds heroes into the array, and can reset the array.
和模板相伴的组件类可以提供英雄数组,能把新的英雄添加到数组中,还能重置英雄数组。
<code-example path="pipes/src/app/flying-heroes.component.ts" region="v1" title="src/app/flying-heroes.component.ts (v1)" linenums="false">
</code-example>
@ -326,10 +458,19 @@ You can add heroes and Angular updates the display when you do.
If you click the `reset` button, Angular replaces `heroes` with a new array of the original heroes and updates the display.
If you added the ability to remove or change a hero, Angular would detect those changes and update the display as well.
我们可以添加新的英雄加完之后Angular就会更新显示。
`reset`按钮会把`heroes`替换成一个由原来的英雄组成的新数组重置完之后Angular就会更新显示。
如果我们提供了删除或修改英雄的能力Angular也会检测到那些更改并更新显示。
### Flying-heroes pipe
### “会飞的英雄”管道
Add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroes to just those heroes who can fly.
我们来往`*ngFor`重复器中添加一个`FlyingHeroesPipe`管道,这个管道能过滤出所有会飞的英雄。
<code-example path="pipes/src/app/flying-heroes.component.html" region="template-flying-heroes" title="src/app/flying-heroes.component.html (flyers)" linenums="false">
</code-example>
@ -338,6 +479,9 @@ Add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroe
Here's the `FlyingHeroesPipe` implementation, which follows the pattern for custom pipes described earlier.
下面是`FlyingHeroesPipe`的实现,它遵循了我们以前见过的那些写自定义管道的模式。
<code-example path="pipes/src/app/flying-heroes.pipe.ts" region="pure" title="src/app/flying-heroes.pipe.ts" linenums="false">
</code-example>
@ -347,11 +491,19 @@ Here's the `FlyingHeroesPipe` implementation, which follows the pattern for cust
Notice the odd behavior in the <live-example></live-example>:
when you add flying heroes, none of them are displayed under "Heroes who fly."
当运行<live-example></live-example>时,我们看到一种奇怪的行为。添加的每个英雄都是会飞行的英雄,但是没有一个被显示出来。
Although you're not getting the behavior you want, Angular isn't broken.
It's just using a different change-detection algorithm that ignores changes to the list or any of its items.
虽然我们没有得到期望的行为但Angular也没有出错。
这里只是用了另一种变更检测算法 —— 它会忽略对列表及其子项所做的任何更改。
Notice how a hero is added:
来看看我们是如何添加新英雄的:
<code-example path="pipes/src/app/flying-heroes.component.ts" region="push" title="src/app/flying-heroes.component.ts" linenums="false">
</code-example>
@ -361,15 +513,25 @@ Notice how a hero is added:
You add the 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的角度来看*这是同一个数组,没有变化,也就不需要更新显示*。
To fix that, create an 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 you *mutate* the array, no pipe is invoked and the display isn't updated;
if you *replace* the array, the pipe executes and the display is updated.
The Flying Heroes application extends the
code with checkbox switches and additional displays to help you experience these effects.
如果我们**修改了**这个数组,没有管道被执行,也没有显示被更新。
如果我们**替换了**这个数组,管道就会被执行,显示也更新了。
这个*飞行英雄*的例子用检查框和其它显示内容扩展了原有代码,来帮我们体验这些效果。
<figure class='image-display'>
<img src='assets/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes"></img>
@ -382,6 +544,10 @@ When do you replace the array? When the data change.
That's an easy rule to follow in *this* example
where the only way to change the data is by adding a hero.
直接替换这个数组是通知Angular更新显示的一种高效方式。
我们该什么时候替换这个数组呢?当数据变化的时候。
在这个*玩具级*例子中,这是一个简单的规则,因为这里修改数据的唯一途径就是添加新英雄。
More often, you don't know when the data have changed,
especially in applications that mutate data in many ways,
perhaps in application locations far away.
@ -390,17 +556,29 @@ Moreover, it's unwise to distort the component design to accommodate a pipe.
Strive to keep the component class independent of the HTML.
The component should be unaware of pipes.
更多情况下,我们不知道什么时候数据变化了,尤其是在那些有很多种途径改动数据的程序中 —— 可能在程序中很远的地方。
组件就是一个通常无法知道那些改动的例子。此外,它会导致削足适履 —— 扭曲我们的组件设计来适应管道。
我们要尽可能保持组件类独立于HTML。组件不应该关心管道的存在。
For filtering flying heroes, consider an *impure pipe*.
为了过滤会飞的英雄,我们要使用*非纯(impure)管道*。
## Pure and impure pipes
## 纯(pure)管道与非纯(impure)管道
There are two categories of pipes: *pure* and *impure*.
Pipes are pure by default. Every pipe you've seen so far has been pure.
You make a pipe impure by setting its pure flag to false. You could make the `FlyingHeroesPipe`
impure like this:
有两类管道:**纯**的与**非纯**的。
默认情况下,管道都是纯的。我们以前见到的每个管道都是纯的。
通过把它的`pure`标志设置为`false`,我们可以制作一个非纯管道。我们可以像这样让`FlyingHeroesPipe`变成非纯的:
<code-example path="pipes/src/app/flying-heroes.pipe.ts" region="pipe-decorator" title="src/app/flying-heroes.pipe.ts" linenums="false">
@ -410,23 +588,40 @@ impure like this:
Before doing that, understand the difference between pure and impure, starting with a pure pipe.
在继续往下走之前,我们先理解一下*纯*和*非纯*之间的区别,从*纯*管道开始。
### Pure pipes
### 纯管道
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`)的更改。
Angular ignores changes within (composite) objects.
It won't call a pure pipe if you 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 it's also fast.
An object reference check is fast&mdash;much faster than a deep check for
differences&mdash;so Angular can quickly determine if it can skip both the
pipe execution and a view update.
这可能看起来是一种限制,但它保证了速度。
对象引用的检查是非常快的(比递归的深检查要快得多)所以Angular可以快速的决定是否应该跳过管道执行和视图更新。
For this reason, a pure pipe is preferable when you can live with the change detection strategy.
When you can't, you *can* use the impure pipe.
因此,如果我们要和变更检测策略打交道,就会更喜欢用纯管道。
如果不能,我们就*可以*转回到非纯管道。
<div class="l-sub-section">
@ -434,7 +629,10 @@ When you can't, you *can* use the impure pipe.
Or you 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 that's discussed later in this page.
a point that's discussed laterin this page.
或者我们也可以完全不用管道。
有时候,使用组件的属性能比用管道更好的达到目的,这一点我们等后面会再提起。
</div>
@ -443,21 +641,35 @@ a point that's discussed later in this page.
### Impure pipes
### 非纯管道
Angular executes an *impure pipe* during every component change detection cycle.
An impure pipe is called often, as often as every keystroke or mouse-move.
Angular会在每个组件的变更检测周期中执行*非纯管道*。
非纯管道可能会被调用很多次,和每个按键或每次鼠标移动一样频繁。
With that concern in mind, implement an impure pipe with great care.
An expensive, long-running pipe could destroy the user experience.
要在脑子里绷着这根弦,我们必须小心翼翼的实现非纯管道。
一个昂贵、迟钝的管道将摧毁用户体验。
{@a impure-flying-heroes}
### An impure *FlyingHeroesPipe*
### 非纯版本的*FlyingHeroesPipe*
A flip of the switch turns the `FlyingHeroesPipe` into a `FlyingHeroesImpurePipe`.
The complete implementation is as follows:
我们把`FlyingHeroesPipe`换成了`FlyingHeroesImpurePipe`。
下面是完整的实现:
<code-tabs>
<code-pane title="FlyingHeroesImpurePipe" path="pipes/src/app/flying-heroes.pipe.ts" region="impure">
@ -475,8 +687,13 @@ The complete implementation is as follows:
You 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`函数又小又快。
<code-example path="pipes/src/app/flying-heroes.pipe.ts" linenums="false" title="src/app/flying-heroes.pipe.ts (filter)" region="filter">
@ -486,6 +703,9 @@ This is a good candidate for an impure pipe because the `transform` function is
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
我们可以从`FlyingHeroesComponent`派生出一个`FlyingHeroesImpureComponent`。
<code-example path="pipes/src/app/flying-heroes-impure.component.html" linenums="false" title="src/app/flying-heroes-impure.component.html (excerpt)" region="template-flying-heroes">
@ -497,6 +717,9 @@ The only substantive change is the pipe in the template.
You can confirm in the <live-example></live-example> that the _flying heroes_
display updates as you add heroes, even when you mutate the `heroes` array.
唯一的重大改动就是管道。
我们可以在<live-example></live-example>中确认,当我们输入新的英雄甚至修改#[code heroes]数组时,这个#[i 会飞的英雄]的显示也跟着更新了。
<h3 id='async-pipe'>
The impure <i>AsyncPipe</i>
@ -504,17 +727,31 @@ display updates as you add heroes, even when you mutate the `heroes` array.
<h3 id='async-pipe'>
非纯 <i>AsyncPipe</i>
</h3>
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 values.
Angular的`AsyncPipe`是一个有趣的非纯管道的例子。
`AsyncPipe`接受一个`Promise`或`Observable`作为输入,并且自动订阅这个输入,最终返回它们给出的值。
The `AsyncPipe` is also stateful.
The pipe maintains a subscription to the input `Observable` and
keeps delivering values from that `Observable` as they arrive.
`AsyncPipe`管道是有状态的。
该管道维护着一个所输入的`Observable`的订阅,并且持续从那个`Observable`中发出新到的值。
This next example binds an `Observable` of message strings
(`message$`) to a view with the `async` pipe.
在下面例子中,我们使用该`async`管道把一个消息字符串(`message$`)的`Observable`绑定到视图中。
<code-example path="pipes/src/app/hero-async-message.component.ts" title="src/app/hero-async-message.component.ts">
@ -528,16 +765,30 @@ extract the resolved values and expose them for binding,
and have to unsubscribe when it's destroyed
(a potent source of memory leaks).
这个Async管道节省了组件的样板代码。
组件不用订阅这个异步数据源,而且不用在被销毁时取消订阅(如果订阅了而忘了反订阅容易导致隐晦的内存泄露)。
### An impure caching pipe
### 一个非纯而且带缓存的管道
Write one more impure pipe, a pipe that makes an HTTP request.
我们来写更多的非纯管道一个向服务器发起HTTP请求的管道。
Remember that impure pipes are called every few milliseconds.
If you're not careful, this pipe will punish the server with requests.
时刻记住,非纯管道可能每隔几微秒就会被调用一次。
如果我们不小心点,这个管道就会发起一大堆请求“攻击”服务器。
In the following code, the pipe only calls the server when the request URL changes and it caches the server response.
The code uses the [Angular http](guide/server-communication) client to retrieve data</span>:
我们确实得小心点。
这个管道只有当所请求的URL发生变化时才会向服务器发起请求。它会缓存服务器的响应。
代码如下,它使用[Angular http](guide/server-communication)客户端来接收数据
<code-example path="pipes/src/app/fetch-json.pipe.ts" title="src/app/fetch-json.pipe.ts">
@ -548,6 +799,8 @@ The code uses the [Angular http](guide/server-communication) client to retrieve
Now demonstrate it in a harness component whose template defines two bindings to this pipe,
both requesting the heroes from the `heroes.json` file.
接下来我们用一个测试台组件演示一下它,该组件的模板中定义了两个使用到此管道的绑定,他们都从`heroes.json`文件中取得英雄数据。
<code-example path="pipes/src/app/hero-list.component.ts" title="src/app/hero-list.component.ts">
@ -557,6 +810,8 @@ both requesting the heroes from the `heroes.json` file.
The component renders as the following:
组件渲染起来是这样的:
<figure class='image-display'>
<img src='assets/images/devguide/pipes/hero-list.png' alt="Hero List"></img>
@ -566,15 +821,28 @@ The component renders as the following:
A breakpoint on the pipe's request for data shows the following:
这个管道上的断点请求数据的过程显示:
* Each binding gets its own pipe instance.
每个绑定都有它自己的管道实例。
* Each pipe instance caches its own URL and data.
每个管道实例都缓存了它自己的URL和数据。
* Each pipe instance only calls the server once.
每个管道实例都只调用一次服务器。
### *JsonPipe*
In the previous code sample, the second `fetch` pipe binding demonstrates more pipe chaining.
It displays the same hero data in JSON format by chaining through to the built-in `JsonPipe`.
第二个绑定除了用到`FetchPipe`之外还链接了更多管道。
我们把获取数据的结果同时显示在第一个绑定和第二个绑定中。第二个绑定中,我们通过链接到一个内置管道`JsonPipe`把它转成了JSON格式。
<div class="callout is-helpful">
@ -586,10 +854,18 @@ It displays the same hero data in JSON format by chaining through to the built-i
<header>
借助json管道进行调试
</header>
The [JsonPipe](api/common/index/JsonPipe-pipe)
provides an easy way to diagnosis a mysteriously failing data binding or
inspect an object for future binding.
[JsonPipe](api/common/index/JsonPipe-class)为你诊断数据绑定的某些神秘错误或为做进一步绑定而探查数据时,提供了一个简单途径。
</div>
@ -600,30 +876,51 @@ inspect an object for future binding.
### 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 discussed earlier in this page are implemented with pure functions.
The built-in `DatePipe` is a pure pipe with a pure function implementation.
So are the `ExponentialStrengthPipe` and `FlyingHeroesPipe`.
A few steps back, you reviewed the `FlyingHeroesImpurePipe`&mdash;an impure pipe with a pure function.
我们在本章前面见过的管道都是用纯函数实现的。
内置的`DatePipe`就是一个用纯函数实现的纯管道。
`ExponentialStrengthPipe`是如此,
`FlyingHeroesComponent`也是如此。
不久前我们刚看过的`FlyingHeroesImpurePipe`,是一个*用纯函数实现的非纯管道*。
But always implement a *pure pipe* with a *pure function*.
Otherwise, you'll see many console errors regarding expressions that changed after they were checked.
但是一个*纯管道*必须总是用*纯函数*实现。忽略这个警告将导致失败并带来一大堆这样的控制台错误:表达式在被检查后被变更。
## Next steps
## 下一步
Pipes are a great way to encapsulate and share common display-value
transformations. Use them like styles, dropping them
into your template's expressions to enrich the appeal and usability
of your views.
管道能很好的封装和共享的通用“值-显示”转换逻辑。我们可以像样式一样使用它们,把它们扔到模板表达式中,以提升视图的表现力和可用性。
Explore Angular's inventory of built-in pipes in the [API Reference](api/#!?query=pipe).
Try writing a custom pipe and perhaps contributing it to the community.
要浏览Angular的所有内置管道请到[API参考手册](api/#!?query=pipe)。
学着写写自定义管道,并贡献给开发社区。
{@a no-filter-pipe}
@ -631,28 +928,46 @@ Try writing a custom pipe and perhaps contributing it to the community.
## Appendix: No *FilterPipe* or *OrderByPipe*
## 附录:没有*FilterPipe*或者*OrderByPipe*
Angular doesn't provide pipes for filtering or sorting lists.
Developers familiar with AngularJS know these as `filter` and `orderBy`.
There are no equivalents in Angular.
Angular没有随身发布过滤或列表排序的管道。
熟悉AngularJS的开发人员应该知道`filter`和`orderBy`过滤器但在Angular中它们没有等价物。
This isn't an oversight. Angular doesn't offer such pipes because
they perform poorly and prevent aggressive minification.
Both `filter` and `orderBy` require parameters that reference object properties.
Earlier in this page, you learned that such pipes must be [impure](guide/pipes#pure-and-impure-pipes) and that
Angular calls impure pipes in almost every change-detection cycle.
这并不是疏忽。Angular不想提供这些管道因为 (a) 它们性能堪忧,以及 (b) 它们会阻止比较激进的代码最小化(minification)。
无论是`filter`还是`orderBy`都需要它的参数引用对象型属性。
我们前面学过,这样的管道必然是[*非纯管道*](guide/pipes#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.
`filter` and `orderBy` have often been abused in AngularJS apps, leading to complaints that Angular itself is slow.
That charge is fair in the indirect sense that AngularJS prepared this performance trap
by offering `filter` and `orderBy` in the first place.
过滤、 特别是排序是昂贵的操作。
当Angular每秒调用很多次这类管道函数时即使是中等规模的列表都可能严重降低用户体验。
在AngularJS程序中`filter`和`orderBy`经常被误用结果连累到Angular自身人们抱怨说它太慢。
从某种意义上这也不冤谁叫AngularJS把`filter`和`orderBy`作为首发队员呢?是它自己准备了这个性能陷阱。
The minification hazard is also compelling, if less obvious. Imagine a sorting pipe applied to a list of heroes.
The list might be sorted by hero `name` and `planet` of origin properties in the following way:
虽然不是很明显,但代码最小化方面也存在风险。想象一个用于英雄列表的排序管道。我们可能根据英雄原始属性中的`name`和`planet`进行排序,就像这样:
<code-example language="html">
&lt;!-- NOT REAL CODE! -->
&lt;div *ngFor="let hero of heroes | orderBy:'name,planet'">&lt;/div>
</code-example>
@ -662,10 +977,18 @@ You identify the sort fields by text strings, expecting the pipe to reference a
Unfortunately, aggressive minification manipulates the `Hero` property names so that `Hero.name` and `Hero.planet`
become something like `Hero.a` and `Hero.b`. Clearly `hero['name']` doesn't work.
我们使用文本字符串来标记出排序字段,期望管道通过索引形式(如`hero['name']`)引用属性的值。
不幸的是,激进的代码最小化策略会*改变*`Hero`类的属性名,所以`Hero.name`和`Hero.planet`可能会被变成`Hero.a`和`Hero.b`。
显然,`hero['name']`是无法正常工作的。
While some may not care to minify this aggressively,
the Angular product shouldn't prevent anyone from minifying aggressively.
Therefore, the Angular team decided that everything Angular provides will minify safely.
我们中的一些人可能不想做那么激进的最小化。但那不过是*我们的*选择而已。
Angular作为一个产品不应该拒绝那些想做激进的最小化的人。
所以Angular开发组决定随Angular一起发布的每样东西都应该能被安全的最小化。
The Angular team and many experienced Angular developers strongly recommend moving
filtering and sorting logic into the component itself.
The component can expose a `filteredHeroes` or `sortedHeroes` property and take control
@ -673,5 +996,11 @@ 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 don't apply to you, you can always create your own such pipes
(similar to the [FlyingHeroesPipe](guide/pipes#impure-flying-heroes)) or find them in the community.
(similar to the [FlyingHeroesPipe](guide/pipes#impure-flying-heroes)) or find them in the community.
如果你不需要顾虑这些性能和最小化问题,也可以创建自己的管道来实现这些功能(参考[FlyingHeroesPipe](guide/pipes#impure-flying-heroes)中的写法)或到社区中去找找。

View File

@ -4,6 +4,9 @@
Angular applications are made up of _components_.
A _component_ is the combination of an HTML template and a component class that controls a portion of the screen. Here is an example of a component that displays a simple string:
Angular 应用是由*组件*组成的。
*组件*由 HTML 模板和组件类组成,组件类控制视图。下面是一个显示简单字符串的组件:
<code-example path="quickstart/src/app/app.component.ts" title="src/app/app.component.ts" linenums="false">
@ -21,6 +24,9 @@ Try this **<live-example noDownload>QuickStart example on Plunker</live-example>
Try it locally with the [***QuickStart seed***](guide/guide/setup "Setup for local development with the QuickStart seed")
and prepare for development of a real Angular application.
试试这个**<live-example noDownload>Plunker上的QuickStart范例</live-example>**。
也可以在本地使用[***QuickStart种子工程***](guide/guide/setup "Setup for local development with the QuickStart seed")来为开发真实的Angular应用做好准备。
</div>
@ -29,8 +35,13 @@ and prepare for development of a real Angular application.
Every component begins with an `@Component` [decorator](guide/glossary#decorator '"decorator" explained')
function that takes a _metadata_ object. The metadata object describes how the HTML template and component class work together.
每个组件都以`@Component`[装饰器](guide/glossary#!{_decorator} '"!{_decorator}" explained')函数开始它接受一个_元数据_对象参数。该元素对象描述了 HTML 模板和组件类是如何一起工作的。
The `selector` property tells Angular to display the component inside a custom `<my-app>` tag in the `index.html`.
`selector`属性为 Angular 指定了在`index.html`中的自定义`<my-app>`标签里显示该组件。
<code-example path="quickstart/src/index.html" region="my-app" title="index.html (inside &lt;body&gt;)" linenums="false">
</code-example>
@ -40,12 +51,19 @@ The `selector` property tells Angular to display the component inside a custom `
The `template` property defines a message inside an `<h1>` header.
The message starts with "Hello" and ends with `{{name}}`,
which is an Angular [interpolation binding](guide/guide/displaying-data) expression.
At runtime, Angular replaces `{{name}}` with the value of the component's `name` property.
At runtime, Angular replaces `{{name}}` with the value of the component's `name` property.
Interpolation binding is one of many Angular features you'll discover in this documentation.
`template`属性定义了`<h1>`标题里的一条消息。
该消息以 “Hello” 开始,以 Angular [插值绑定](guide/guide/displaying-data)表达式`{{name}}`结束。
在运行时Angular 用组件的`name`属性值替换`{{name}}`。
插值绑定是 Angular 的特征之一。你将在本文档中探索更多 Angular 的特征。
In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
在这个例子中,把组件类的`name`属性从`'Angular'`改为`'World'`,看看会怎么样。
<div class="callout is-helpful">
@ -57,6 +75,12 @@ In the example, change the component class's `name` property from `'Angular'` to
<header>
关于 TypeScript
</header>
<p>
This example is written in <a href="http://www.typescriptlang.org/" target="_blank" title="TypeScript">TypeScript</a>, a superset of JavaScript. Angular
uses TypeScript because its types make it easy to support developer productivity with tooling. You can also write Angular code in JavaScript; <a href="cookbook/ts-to-js.html">this guide</a> explains how.
@ -65,6 +89,14 @@ In the example, change the component class's `name` property from `'Angular'` to
<p>
本例是用 JavaScript 的一个超集 <a href="http://www.typescriptlang.org/" target="_blank" title="TypeScript">TypeScript</a> 编写的。
Angular 使用 TypeScript 是因为它的类型可以帮助工具提高开发者效率。你也可以用 JavaScript 编写 Angular 代码,参见<a href="cookbook/ts-to-js.html">本指南</a>
</p>
</div>
@ -74,8 +106,12 @@ In the example, change the component class's `name` property from `'Angular'` to
### Next step
### 下一步
Start [**learning Angular**](guide/guide/learning-angular "Learning Angular").
开始[**学习 Angular**](guide/guide/learning-angular "学习 Angular").
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
@title
Security
安全
@intro
Developing for content security in Angular applications.
开发内容安全的 Angular 应用。
@description
@ -13,39 +13,85 @@ protections against common web-application vulnerabilities and attacks such as c
scripting attacks. It doesn't cover application-level security, such as authentication (_Who is
this user?_) and authorization (_What can this user do?_).
Web应用程序的安全涉及到很多方面。针对常见的漏洞和攻击比如跨站脚本攻击Angular提供了一些内置的保护措施。本章将讨论这些内置保护措施但不会涉及应用级安全比如用户认证_这个用户是谁_和授权(_这个用户能做什么_)。
For more information about the attacks and mitigations described below, see [OWASP Guide Project](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).
要了解更多攻防信息,参见[开放式Web应用程序安全项目(OWASP)](https://www.owasp.org/index.php/Category:OWASP_Guide_Project)。
# Contents
# Contents:
# 目录:
* [Reporting vulnerabilities](guide/security#report-issues).
[举报漏洞](guide/security#report-issues).
* [Best practices](guide/security#best-practices).
[最佳实践](guide/security#best-practices).
* [Preventing cross-site scripting (XSS)](guide/security#xss).
[防范跨站脚本(XSS)攻击](guide/security#xss).
* [Trusting safe values](guide/security#bypass-security-apis).
[信任安全值](guide/security#bypass-security-apis).
* [HTTP-Level vulnerabilities](guide/security#http).
[HTTP级别的漏洞](guide/security#http).
* [Auditing Angular applications](guide/security#code-review).
[审计Angular应用程序](guide/security#code-review).
You can run the <live-example></live-example> in Plunker and download the code from there.
运行<live-example></live-example>来试用本页的代码。
<h2 id='report-issues'>
Reporting vulnerabilities
<p>
Reporting vulnerabilities
</p>
<p>
举报漏洞
</p>
</h2>
To report vulnerabilities in Angular itself, email us at [security@angular.io](guide/mailto:security@angular).
给我们([security@angular.io](guide/mailto:security@angular)发邮件报告Angular本身的漏洞。
For more information about how Google handles security issues, see [Google's security
philosophy](https://www.google.com/about/appsecurity/).
要了解关于“谷歌如何处理安全问题”的更多信息,参见[谷歌的安全哲学](https://www.google.com/about/appsecurity/)。
<h2 id='best-practices'>
Best practices
<p>
Best practices
</p>
<p>
最佳实践
</p>
</h2>
@ -55,18 +101,35 @@ We regularly update the Angular libraries, and these updates may fix security de
previous versions. Check the Angular [change
log](https://github.com/angular/angular/blob/master/CHANGELOG.md) for security-related updates.
**及时把Angular包更新到最新版本。**
我们会频繁的更新Angular库这些更新可能会修复之前版本中发现的安全漏洞。查看Angular的[更新记录](https://github.com/angular/angular/blob/master/CHANGELOG.md),了解与安全有关的更新。
* **Don't modify your copy of Angular.**
Private, customized versions of Angular tend to fall behind the current version and may not include
important security fixes and enhancements. Instead, share your Angular improvements with the
community and make a pull request.
**不要修改你的Angular副本。**
私有的、定制版的Angular往往跟不上最新版本这可能导致你忽略重要的安全修复与增强。反之应该在社区共享你对Angular所做的改进并创建Pull Request。
* **Avoid Angular APIs marked in the documentation as “_Security Risk_.”**
For more information, see the [Trusting safe values](guide/security#bypass-security-apis) section of this page.
**避免使用本文档中带“[_安全风险_](guide/security#bypass-security-apis)”标记的Angular API。**
要了解更多信息,请参阅本章的[信任那些安全的值](guide/security#bypass-security-apis)部分。
<h2 id='xss'>
Preventing cross-site scripting (XSS)
<p>
Preventing cross-site scripting (XSS)
</p>
<p>
防范跨站脚本(XSS)攻击
</p>
</h2>
@ -76,18 +139,30 @@ to inject malicious code into web pages. Such code can then, for example, steal
particular, login data) or perform actions to impersonate the user. This is one of the most
common attacks on the web.
To block XSS attacks, you must prevent malicious code from entering the DOM (Document Object Model). For example, if
[跨站脚本(XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting)允许攻击者将恶意代码注入到页面中。这些代码可以偷取用户数据
特别是它们的登录数据还可以冒充用户执行操作。它是Web上最常见的攻击方式之一。
To block XSS attacks, you must prevent malicious code from entering the DOM(Document Object Model). For example, if
attackers can trick you into inserting a `<script>` tag in the DOM, they can run arbitrary code on
your website. The attack isn't limited to `<script>` tags&mdash;many elements and properties in the
DOM allow code execution, for example, `<img onerror="...">` and `<a href="javascript:...">`. If
attacker-controlled data enters the DOM, expect security vulnerabilities.
为了防范XSS攻击我们必须阻止恶意代码进入DOM。比如如果某个攻击者能骗我们把`<script>`DOM
除了`<script>`使DOM`<img onerror="...">``<a href="javascript:...">`
如果攻击者所控制的数据混进了DOM就会导致安全漏洞。
### Angulars cross-site scripting security model
### Angular的“跨站脚本安全模型”
To systematically block XSS bugs, Angular treats all values as untrusted by default. When a value
is inserted into the DOM from a template, via property, attribute, style, class binding, or interpolation,
Angular sanitizes and escapes untrusted values.
为了系统性的防范XSS问题Angular默认把所有值都当做不可信任的。
当值从模板中以属性Property、DOM元素属性Attribte)、CSS类绑定或插值表达式等途径插入到DOM中的时候
Angular将对这些值进行无害化处理Sanitize对不可信的值进行编码。
_Angular templates are the same as executable code_: HTML, attributes, and binding expressions
(but not the values bound) in templates are trusted to be safe. This means that applications must
prevent values that an attacker can control from ever making it into the source code of a
@ -95,28 +170,57 @@ template. Never generate template source code by concatenating user input and te
To prevent these vulnerabilities, use
the [offline template compiler](guide/security#offline-template-compiler), also known as _template injection_.
**Angular的模板同样是可执行的**模板中的HTML、Attribute和绑定表达式还没有绑定到值的时候会被当做可信任的。
这意味着应用必须防止把可能被攻击者控制的值直接编入模板的源码中。永远不要根据用户的输入和原始模板动态生成模板源码!
使用[离线模板编译器](guide/security#offline-template-compiler)是防范这类“模板注入”漏洞的有效途径。
### Sanitization and security contexts
### 无害化处理与安全环境
_Sanitization_ is the inspection of an untrusted value, turning it into a value that's safe to insert into
the DOM. In many cases, sanitization doesn't change a value at all. Sanitization depends on context:
a value that's harmless in CSS is potentially dangerous in a URL.
无害化处理会审查不可信的值并将它们转换成可以安全插入到DOM的形式。多数情况下这些值并不会在处理过程中发生任何变化。
无害化处理的方式取决于所在的环境一个在CSS里面无害的值可能在URL里很危险。
Angular defines the following security contexts:
Angular定义了四个安全环境 - HTML样式URL和资源URL
* **HTML** is used when interpreting a value as HTML, for example, when binding to `innerHtml`.
**HTML**值需要被解释为HTML时使用比如当绑定到`innerHTML`时。
* **Style** is used when binding CSS into the `style` property.
* **URL** is used for URL properties, such as `<a href>`.
**样式**值需要作为CSS绑定到`style`属性时使用。
* **URL** is used for URL properties such as `<a href>`.
**URL**值需要被用作URL属性时使用比如`<a href>`。
* **Resource URL** is a URL that will be loaded and executed as code, for example, in `<script src>`.
**资源URL**:值需要被当做代码而加载并执行时使用,比如`<script src>`中的URL。
Angular sanitizes untrusted values for HTML, styles, and URLs; sanitizing resource URLs isn't
possible because they contain arbitrary code. In development mode, Angular prints a console warning
when it has to change a value during sanitization.
Angular会对前三项中种不可信的值进行无害化处理。但Angular无法对第四种资源URL进行无害化因为它们可能包含任何代码。在开发模式下
如果Angular在进行无害化处理时需要被迫改变一个值它就会在控制台上输出一个警告。
### Sanitization example
### 无害化示例
The following template binds the value of `htmlSnippet`, once by interpolating it into an element's
content, and once by binding it to the `innerHTML` property of an element:
下面的例子绑定了`htmlSnippet`的值,一次把它放进插值表达式里,另一次把它绑定到元素的`innerHTML`属性上。
<code-example path="security/src/app/inner-html-binding.component.html" title="src/app/inner-html-binding.component.html">
@ -127,10 +231,15 @@ content, and once by binding it to the `innerHTML` property of an element:
Interpolated content is always escaped&mdash;the HTML isn't interpreted and the browser displays
angle brackets in the element's text content.
插值表达式的内容总会被编码 - 其中的HTML不会被解释所以浏览器会在元素的文本内容中显示尖括号。
For the HTML to be interpreted, bind it to an HTML property such as `innerHTML`. But binding
a value that an attacker might control into `innerHTML` normally causes an XSS
vulnerability. For example, code contained in a `<script>` tag is executed:
如果希望这段HTML被正常解释就必须绑定到一个HTML属性上比如`innerHTML`。但是如果把一个可能被攻击者控制的值绑定到`innerHTML`就会导致XSS漏洞。
比如,包含在`<script>`
<code-example path="security/src/app/inner-html-binding.component.ts" linenums="false" title="src/app/inner-html-binding.component.ts (class)" region="class">
@ -141,6 +250,8 @@ vulnerability. For example, code contained in a `<script>` tag is executed:
Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `<script>`
tag but keeps safe content such as the text content of the `<script>` tag and the `<b>` element.
Angular认为这些值是不安全的并自动进行无害化处理。它会移除`<script>``<b>`
<figure class='image-display'>
<img src='assets/images/devguide/security/binding-inner-html.png' alt='A screenshot showing interpolated and bound HTML values'></img>
@ -150,25 +261,38 @@ tag but keeps safe content such as the text content of the `<script>` tag and th
### Avoid direct use of the DOM APIs
### 避免直接使用DOM API
The built-in browser DOM APIs don't automatically protect you from security vulnerabilities.
For example, `document`, the node available through `ElementRef`, and many third-party APIs
contain unsafe methods. Avoid directly interacting with the DOM and instead use Angular
templates where possible.
浏览器内置的DOM API不会自动针对安全漏洞进行防护。比如`document`(它可以通过`ElementRef`访问以及其它第三方API都可能包含不安全的方法。
要避免直接与DOM交互只要可能就尽量使用Angular模板。
### Content security policy
### 内容安全策略
Content Security Policy (CSP) is a defense-in-depth
technique to prevent XSS. To enable CSP, configure your web server to return an appropriate
`Content-Security-Policy` HTTP header. Read more about content security policy at
[An Introduction to Content Security Policy](http://www.html5rocks.com/en/tutorials/security/content-security-policy/)
on the HTML5Rocks website.
[内容安全策略(CSP)](https://developer.mozilla.org/en-)是用来防范XSS的纵深防御技术。
要打开CSP请配置你的Web服务器让它返回合适的HTTP头`Content_Security_Policy`。
要了解关于内容安全策略的更多信息请参阅HTML5Rocks上的[内容安全策略简介](http://www.html5rocks.com/en/tutorials/security/content-security-policy/)
{@a offline-template-compiler}
### Use the offline template compiler
### 使用离线模板编译器
The offline template compiler prevents a whole class of vulnerabilities called template injection,
and greatly improves application performance. Use the offline template compiler in production
deployments; don't dynamically generate templates. Angular trusts template code, so generating
@ -176,8 +300,14 @@ templates, in particular templates containing user data, circumvents Angular's b
For information about dynamically constructing forms in a safe way, see the
[Dynamic Forms](cookbook/dynamic-form) cookbook page.
离线模板编译器阻止了一整套被称为“模板注入”的漏洞,并能显著增强应用程序的性能。尽量在产品发布时使用离线模板编译器,
而不要动态生成模板比如在代码中拼接字符串生成模板。由于Angular会信任模板本身的代码所以动态生成的模板 —— 特别是包含用户数据的模板 —— 会绕过Angular自带的保护机制。
要了解如何用安全的方式动态创建表单,请参见[动态表单烹饪宝典](cookbook/dynamic-form)一章。
### Server-side XSS protection
### 服务器端XSS保护
HTML constructed on the server is vulnerable to injection attacks. Injecting template code into an
Angular application is the same as injecting executable code into the
application: it gives the attacker full control over the application. To prevent this,
@ -185,6 +315,10 @@ use a templating language that automatically escapes values to prevent XSS vulne
the server. Don't generate Angular templates on the server side using a templating language; doing this
carries a high risk of introducing template-injection vulnerabilities.
服务器端构造的HTML很容易受到注入攻击。当需要在服务器端生成HTML时比如Angular应用的初始页面
务必使用一个能够自动进行无害化处理以防范XSS漏洞的后端模板语言。不要在服务器端使用模板语言生成Angular模板
这样会带来很高的“模板注入”风险。
<h2 id='bypass-security-apis'>
@ -193,6 +327,12 @@ carries a high risk of introducing template-injection vulnerabilities.
<h2 id='bypass-security-apis'>
信任安全值
</h2>
Sometimes applications genuinely need to include executable code, display an `<iframe>` from some
URL, or construct potentially dangerous URLs. To prevent automatic sanitization in any of these
situations, you can tell Angular that you inspected a value, checked how it was generated, and made
@ -200,19 +340,27 @@ sure it will always be secure. But *be careful*. If you trust a value that might
are introducing a security vulnerability into your application. If in doubt, find a professional
security reviewer.
有时候应用程序确实需要包含可执行的代码比如使用URL显示`<iframe>`或者构造出有潜在危险的URL。
为了防止在这种情况下被自动无害化你可以告诉Angular我已经审查了这个值检查了它是怎么生成的并确信它总是安全的。
但是**千万要小心**!如果你信任了一个可能是恶意的值,就会在应用中引入一个安全漏洞。如果你有疑问,请找一个安全专家复查下。
To mark a value as trusted, inject `DomSanitizer` and call one of the
following methods:
* `bypassSecurityTrustHtml`
* `bypassSecurityTrustScript`
* `bypassSecurityTrustStyle`
* `bypassSecurityTrustUrl`
* `bypassSecurityTrustResourceUrl`
注入`DomSanitizer`服务,然后调用下面的方法之一,你就可以把一个值标记为可信任的。
* `bypassSecurityTrustHtml`
* `bypassSecurityTrustScript`
* `bypassSecurityTrustStyle`
* `bypassSecurityTrustUrl`
* `bypassSecurityTrustResourceUrl`
Remember, whether a value is safe depends on context, so choose the right context for
your intended use of the value. Imagine that the following template needs to bind a URL to a
`javascript:alert(...)` call:
记住,一个值是否安全取决于它所在的环境,所以你要为这个值按预定的用法选择正确的环境。假设下面的模板需要把`javascript.alert(...)`方法绑定到URL。
<code-example path="security/src/app/bypass-security.component.html" linenums="false" title="src/app/bypass-security.component.html (URL)" region="URL">
@ -224,6 +372,8 @@ Normally, Angular automatically sanitizes the URL, disables the dangerous code,
in development mode, logs this action to the console. To prevent
this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
通常Angular会自动无害化这个URL并禁止危险的代码。为了防止这种行为我们可以调用`bypassSecurityTrustUrl`把这个URL值标记为一个可信任的URL
<code-example path="security/src/app/bypass-security.component.ts" linenums="false" title="src/app/bypass-security.component.ts (trust-url)" region="trust-url">
@ -244,6 +394,10 @@ context, because an untrusted source can, for example, smuggle in file downloads
could execute. So call a method on the controller to construct a trusted video URL, which causes
Angular to allow binding into `<iframe src>`:
如果需要把用户输入转换为一个可信任的值我们可以很方便的在控制器方法中处理。下面的模板允许用户输入一个YouTube视频的ID
然后把相应的视频加载到`<iframe>`中。`<iframe src>`是一个“资源URL”的安全环境因为不可信的源码可能作为文件下载到本地被毫无防备的用户执行。
所以我们要调用一个控制器方法来构造一个新的、可信任的视频URL然后把它绑定到`<iframe src>`。
<code-example path="security/src/app/bypass-security.component.html" linenums="false" title="src/app/bypass-security.component.html (iframe)" region="iframe">
@ -264,10 +418,19 @@ Angular to allow binding into `<iframe src>`:
<h2 id='http'>
HTTP级别的漏洞
</h2>
Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request
forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily
on the server side, but Angular provides helpers to make integration on the client side easier.
Angular内置了一些支持来防范两个常见的HTTP漏洞跨站请求伪造XSRF和跨站脚本包含XSSI
这两个漏洞主要在服务器端防范但是Angular也自带了一些辅助特性可以让客户端的集成变得更容易。
<h3 id='xsrf'>
Cross-site request forgery
@ -275,56 +438,96 @@ on the server side, but Angular provides helpers to make integration on the clie
<h3 id='xsrf'>
跨站请求伪造XSRF
</h3>
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting
a different web page (such as `evil.com`) with malignant code that secretly sends a malicious request
to the application's web server (such as `example-bank.com`).
在跨站请求伪造XSRF或CSFR攻击者欺骗用户让他们访问一个假冒页面(例如`evil.com`)
该页面带有恶意代码,秘密的向你的应用程序服务器发送恶意请求(例如`example-bank.com`)。
Assume the user is logged into the application at `example-bank.com`.
The user opens an email and clicks a link to `evil.com`, which opens in a new tab.
假设用户已经在`example-bank.com`登录。用户打开一个邮件,点击里面的链接,在新页面中打开`evil.com`。
The `evil.com` page immediately sends a malicious request to `example-bank.com`.
Perhaps it's a request to transfer money from the user's account to the attacker's account.
The browser automatically sends the `example-bank.com` cookies (including the authentication cookie) with this request.
If the `example-bank.com` server lacks XSRF protection, it can't tell the difference between a legitimate
request from the application and the forged request from `evil.com`.
该`evil.com`页面立刻发送恶意请求到`example-bank.com`。这个请求可能是从用户账户转账到攻击者的账户。
与该请求一起,浏览器自动发出`example-bank.com`的cookie。
If the `example-bank.com` server lacks XSRF protection, itcan't tell the difference between a legitimate request from the application
and the forged request from `evil.com`.
如果`example-bank.com`服务器缺乏XSRF保护就无法辨识请求是从应用程序发来的合法请求还是从`evil.com`来的假请求。
To prevent this, the application must ensure that a user request originates from the real
application, not from a different site.
The server and client must cooperate to thwart this attack.
为了防止这种情况,你必须确保每个用户的请求都是从你自己的应用中发出的,而不是从另一个网站发出的。
客户端和服务器必须合作来抵挡这种攻击。
In a common anti-XSRF technique, the application server sends a randomly
generated authentication token in a cookie.
The client code reads the cookie and adds a custom request header with the token in all subsequent requests.
The server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match.
常见的反XSRF技术是服务器随机生成一个用户认证令牌到cookie中。
客户端代码获取这个cookie并用它为接下来所有的请求添加自定义请求页头。
服务器比较收到的cookie值与请求页头的值如果它们不匹配便拒绝请求。
This technique is effective because all browsers implement the _same origin policy_. Only code from the website
on which cookies are set can read the cookies from that site and set custom headers on requests to that site.
That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.
这个技术之所以有效是因为所有浏览器都实现了_同源策略_。只有设置cookie的网站的代码可以访问该站的cookie并为该站的请求设置自定义页头。
这就是说只有你的应用程序可以获取这个cookie令牌和设置自定义页头。`evil.com`的恶意代码不能。
Angular's `http` has built-in support for the client-side half of this technique in its `XSRFStrategy`.
The default `CookieXSRFStrategy` is turned on automatically.
Before sending an HTTP request, the `CookieXSRFStrategy` looks for a cookie called `XSRF-TOKEN` and
sets a header named `X-XSRF-TOKEN` with the value of that cookie.
Angular的`http`客户端在其`XSRFStrategy`中具有对这项技术的内置支持。
默认的`CookieXSRFStrategy`会被自动开启
在发送每个请求之前,`CookieXSRFStrategy`查询名为`XSRF-TOKEN`的cookie并设置一个名为`X-XSRF-TOKEN`的HTTP请求头并把该cookie的值赋给它。
The server must do its part by setting the
initial `XSRF-TOKEN` cookie and confirming that each subsequent state-modifying request
includes a matching `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header.
服务器必须要完成自己的任务,设置初始`XSRF-TOKEN`cookie并确认接下来的每个请求包含了配对的`XSRF-TOKEN`cookie和`X-XSRF-TOKEN`页头。
XSRF/CSRF tokens should be unique per user and session, have a large random value generated by a
cryptographically secure random number generator, and expire in a day or two.
cryptographically secure random number generator, and expire in a day or two.
CSRF令牌对每个用户和session应该是唯一的它包含一大串由安全的随机数字生成器生成的随机值并且在一两天之内过期。
Your server may use a different cookie or header name for this purpose.
An Angular application can customize cookie and header names by providing its own `CookieXSRFStrategy` values.
你的服务器可能使用不同的cookie或者页头名字。Angular应用可以通过自己的`CookieXSRFStrategy`值来自定义cookie和页头名字。
<code-example language="typescript">
{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }
</code-example>
Or you can implement and provide an entirely custom `XSRFStrategy`:
或者你可以实现和提供完整的自定义`XSRFStrategy`
<code-example language="typescript">
{ provide: XSRFStrategy, useClass: MyXSRFStrategy }
@ -339,9 +542,15 @@ For information about CSRF at the Open Web Application Security Project (OWASP),
The Stanford University paper
<a href="https://seclab.stanford.edu/websec/csrf/csrf.pdf" target="_blank">Robust Defenses for Cross-Site Request Forgery</a> is a rich source of detail.
到开放式Web应用程序安全项目(OWASP)的[这里](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29)
和[这里](https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet)学习更多关于跨站请求伪造XSRF的知识。
这个[斯坦福大学论文](https://seclab.stanford.edu/websec/csrf/csrf.pdf)有详尽的细节。
See also Dave Smith's easy-to-understand
<a href="https://www.youtube.com/watch?v=9inczw6qtpY" target="_blank" title="Cross Site Request Funkery Securing Your Angular Apps From Evil Doers">talk on XSRF at AngularConnect 2016</a>.
参见Dave Smith在<a href="https://www.youtube.com/watch?v=9inczw6qtpY" target="_blank" title="Cross Site Request Funkery Securing Your Angular Apps From Evil Doers">AngularConnect 2016关于XSRF的演讲</a>
<h3 id='xssi'>
Cross-site script inclusion (XSSI)
@ -349,20 +558,36 @@ See also Dave Smith's easy-to-understand
<h3 id='xssi'>
跨站脚本包含(XSSI)
</h3>
Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to
read data from a JSON API. The attack works on older browsers by overriding native JavaScript
object constructors, and then including an API URL using a `<script>` tag.
跨站脚本包含也被称为Json漏洞它可以允许一个攻击者的网站从JSON API读取数据。这种攻击发生在老的浏览器上
它重写原生JavaScript对象的构造函数然后使用`<script>`APIURL
This attack is only successful if the returned JSON is executable as JavaScript. Servers can
prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the
well-known string `")]}',\n"`.
只有在返回的JSON能像JavaScript一样可以被执行时这种攻击才会生效。所以服务端会约定给所有JSON响应体加上前缀`")]}',\n"`,来把它们标记为不可执行的,
以防范这种攻击。
Angular's `Http` library recognizes this convention and automatically strips the string
`")]}',\n"` from all responses before further parsing.
Angular的`Http`库会识别这种约定,并在进一步解析之前,自动把字符串`")]}',\n"`从所有响应中去掉。
For more information, see the XSSI section of this [Google web security blog
post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html).
要学习更多这方面的知识,请参见[谷歌Web安全博客文章](https://security.googleblog.com/2011/05/website-security-for-webmasters.html)的XSSI小节。
<h2 id='code-review'>
@ -371,7 +596,16 @@ post](https://security.googleblog.com/2011/05/website-security-for-webmasters.ht
<h2 id='code-review'>
审计Angular应用程序
</h2>
Angular applications must follow the same security principles as regular web applications, and
must be audited as such. Angular-specific APIs that should be audited in a security review,
such as the [_bypassSecurityTrust_](guide/security#bypass-security-apis) methods, are marked in the documentation
as security sensitive.
as security sensitive.
Angular应用应该遵循和常规Web应用一样的安全原则并按照这些原则进行审计。Angular中某些应该在安全评审中被审计的API
比如[_bypassSecurityTrust_](guide/security#bypass-security-apis) API都在文档中被明确标记为安全性敏感的。

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
@title
Set the Document Title
设置文档标题
@intro
Setting the document or window title using the Title service.
使用 Title 服务来设置文档标题或窗口标题
@description
@ -13,8 +13,12 @@ Setting the document or window title using the Title service.
Your app should be able to make the browser title bar say whatever you want it to say.
This cookbook explains how to do it.
应用程序应该能让浏览器标题栏显示我们想让它显示的内容。本*烹饪宝典*解释怎么做。
See the <live-example name="cb-set-document-title"></live-example>.
**参见<live-example name="cb-set-document-title"></live-example>**。
<table>
@ -26,6 +30,8 @@ See the <live-example name="cb-set-document-title"></live-example>.
To see the browser title bar change in the live example,
open it again in the Plunker editor by clicking the icon in the upper right,
then pop out the preview window by clicking the blue 'X' button in the upper right corner.
要在在线例子中看到浏览器标题的变化请点击右上角的图标在Plunker编辑器中打开它然后点击预览窗口右上角的蓝色'X'按钮,弹出窗口。
</td>
<td>
@ -40,10 +46,16 @@ See the <live-example name="cb-set-document-title"></live-example>.
## The problem with *&lt;title&gt;*
## *&lt;title&gt;*的问题
The obvious approach is to bind a property of the component to the HTML `<title>` like this:
显而易见的方法是把组件的属性绑定到HTML的`<title>`标签上,像这样:
<code-example format=''>
&lt;title&gt;{{This_Does_Not_Work}}&lt;/title&gt;
</code-example>
@ -52,9 +64,14 @@ Sorry but that won't work.
The root component of the application is an element contained within the `<body>` tag.
The HTML `<title>` is in the document `<head>`, outside the body, making it inaccessible to Angular data binding.
抱歉,这样不行。我们应用程序的根组件是一个包含在`<body>`标签里的元素。该HTML的`<title>`在文档的`<head>`元素里,在`<body>`之外Angular的数据绑定无法访问到它。
You could grab the browser `document` object and set the title manually.
That's dirty and undermines your chances of running the app outside of a browser someday.
可以从浏览器获得`document`对象,并且手动设置标题。但是这样看起来很脏,而且将无法在浏览器之外运行应用程序。
<div class="l-sub-section">
@ -64,21 +81,40 @@ pre-rendering for near-instant first app render times and for SEO. It means you
inside a Web Worker to improve your app's responsiveness by using multiple threads. And it
means that you could run your app inside Electron.js or Windows Universal to deliver it to the desktop.
在浏览器外运行应用程序意味着利用服务器端预先渲染为应用程序实现几乎实时的首次渲染同时还能支持SEO(搜索引擎优化)。
意味着你可以在一个Web Worker中运行你的应用程序通过多线程技术增强应用程序的响应性。
还意味着你可以在Electron.js或者Windows Universal里面运行发布到桌面环境。
</div>
## Use the *Title* service
## 使用*Title*服务
Fortunately, Angular bridges the gap by providing a `Title` service as part of the *Browser platform*.
The [Title](api/platform-browser/index/Title-class) service is a simple class that provides an API
for getting and setting the current HTML document title:
幸运的是Angular在*浏览器平台*的包中,提供了一个`Title`服务,弥补了这种差异。
[Title](api/platform/browser/Title-class)服务是一个简单的类提供了一个API用来获取和设置当前HTML文档的标题。
* `getTitle() : string`&mdash;Gets the title of the current HTML document.
`getTitle(): string` —— 获取当前HTML文档的标题。
* `setTitle( newTitle : string )`&mdash;Sets the title of the current HTML document.
`setTitle( newTitle: string)` —— 设置当前HTML文档的标题。
You can inject the `Title` service into the root `AppComponent` and expose a bindable `setTitle` method that calls it:
我们来把`Title`服务注入到根组件`AppComponent`,并暴露出可供绑定的`setTitle`方法让别人来调用该服务:
<code-example path="cb-set-document-title/src/app/app.component.ts" region="class" title="src/app/app.component.ts (class)" linenums="false">
@ -88,6 +124,9 @@ You can inject the `Title` service into the root `AppComponent` and expose a bin
Bind that method to three anchor tags and voilà!
我们把这个方法绑定到三个A标签瞧瞧
<figure class='image-display'>
<img src="assets/images/cookbooks/set-document-title/set-title-anim.gif" alt="Set title"></img>
</figure>
@ -96,6 +135,8 @@ Bind that method to three anchor tags and voilà!
Here's the complete solution:
这里是完整的方案(代码)。
<code-tabs>
@ -118,11 +159,17 @@ Here's the complete solution:
## Why provide the *Title* service in *bootstrap*
## 为什么要在*bootstrap*里面提供这个*Title*服务
Generally you want to provide application-wide services in the root application component, `AppComponent`.
我们通常会推荐在应用程序的根组件`AppComponent`中提供应用程序级的服务。
This cookbook recommends registering the title service during bootstrapping,
a location you reserve for configuring the runtime Angular environment.
但这里我们推荐在引导过程中注册这个Title服务这个位置是为设置Angular运行环境而保留的。
That's exactly what you're doing.
The `Title` service is part of the Angular *browser platform*.
If you bootstrap your application into a different platform,
@ -130,4 +177,9 @@ you'll have to provide a different `Title` service that understands
the concept of a "document title" for that specific platform.
Ideally, the application itself neither knows nor cares about the runtime environment.
[Back to top](guide/set-document-title#top)
我们的做法正是如此。这里的`Title`服务是Angular*浏览器平台*的一部分。如果在其它平台上引导应用程序,就得提供另一个专为那个平台准备的`Title`服务。
[Back to top](guide/set-document-title#top)
[回到顶部](guide/set-document-title#top)

View File

@ -1,8 +1,8 @@
@title
Setup Anatomy
搭建剖析
@intro
Inside the local development environment for SystemJS.
解析 SystemJS 本地开发环境
@description
@ -11,12 +11,19 @@ Inside the local development environment for SystemJS.
The documentation [setup](guide/setup) procedures install a _lot_ of files.
Most of them can be safely ignored.
在[搭建](guide/setup)本地开发环境的过程中会安装*很多*文件。它们大部分都可以被忽略掉。
Application files _inside the_ **`src/`** and **`e2e/`** folders matter most to developers.
对程序员来讲最重要的是在 *`src/`**`e2e/`* 文件夹*之内*的应用文件。
Files _outside_ those folders condition the development environment.
They rarely change and you may never view or modify them.
If you do, this page can help you understand their purpose.
在这两个文件夹*之外*的文件为开发环境设定条件。
这些文件很少会需要变动,你可能永远都不需要阅览或者修改它们。
<style>
td, th {vertical-align: top}
@ -37,11 +44,19 @@ If you do, this page can help you understand their purpose.
<tr>
<th>
File
文件
</th>
<th>
Purpose
用途
</th>
</tr>
@ -55,15 +70,22 @@ If you do, this page can help you understand their purpose.
<td>
Angular application files go here.
Angular application files go here.
你的 Angular 应用文件。
Ships with the "Hello Angular" sample's
`AppComponent`, `AppModule`, a component unit test (`app.component.spec.ts`), and
the bootstrap file, `main.ts`.
Try the <live-example name="setup">sample application</live-example>
"Hello Angular" 这个例子中有 `AppComponent`、`AppModule`、 一个组件单元测试 (`app.component.spec.ts`) 以及引导文件 `main.ts`
Try the <live-example name="setup">sample application</live-example>
and the <live-example name="setup" plnkr="quickstart-specs">unit test</live-example>
as _live examples_.
在live example中试试<live-example name="setup">范例程序</live-example><live-example name="setup" plnkr="quickstart-specs">单元测试</live-example>
</td>
</tr>
@ -82,7 +104,12 @@ If you do, this page can help you understand their purpose.
<a href="http://www.protractortest.org/" target="_blank" title="Protractor: end-to-end testing for Angular">protractor</a>
e2e test runner.
应用的*端对端*(e2e)测试,用 Jasmine 写成并用 <a href="http://www.protractortest.org/" target="_blank" title="Protractor:Angular 的端对端测试">protractor</a> 端对端测试运行器测试。
Initialized with an e2e test for the "Hello Angular" sample.
初始化后有个“Hello Angular” 的例子的端对端测试。
</td>
</tr>
@ -97,6 +124,9 @@ If you do, this page can help you understand their purpose.
The _npm_ packages installed with the `npm install` command.
`npm install` 命令安装的 *npm* 包。
</td>
</tr>
@ -113,8 +143,14 @@ If you do, this page can help you understand their purpose.
<td>
Tooling configuration files and folders.
Tooling configuration files and folders.
配置文件和文件夹的工具。
Ignore them until you have a compelling reason to do otherwise.
除非非常必要,否则可以忽略。
</td>
</tr>
@ -129,7 +165,13 @@ If you do, this page can help you understand their purpose.
The history of changes to the _QuickStart_ repository.
*快速起步*库的更新历史。
Delete or ignore.
删除或忽略。
</td>
</tr>
@ -144,6 +186,9 @@ If you do, this page can help you understand their purpose.
The application icon that appears in the browser tab.
出现在浏览器标签上的应用图标。
</td>
</tr>
@ -162,7 +207,14 @@ If you do, this page can help you understand their purpose.
Then it boots the application, placing the root `AppComponent`
in the custom `<my-app>` body tag.
应用的宿主页面。
它以特定的顺序加载一些基本脚本。
然后它启动应用,将根`AppComponent`放置到自定义`<my-app>`标签里。
The same `index.html` satisfies all documentation application samples.
同一个 `index.html`满足所有文档应用例子。
</td>
</tr>
@ -178,6 +230,9 @@ If you do, this page can help you understand their purpose.
Configuration for the <a href="https://karma-runner.github.io/1.0/index.html" target="_blank" title="Karma unit test runner">karma</a>
test runner described in the [Testing](guide/testing) guide.
在[测试](guide/testing)指南中提到的 <a href="https://karma-runner.github.io/1.0/index.html" target="_blank" title="Karma测试运行器">karma</a> 测试运行器的配置。
</td>
</tr>
@ -193,6 +248,9 @@ If you do, this page can help you understand their purpose.
Script to run <a href="https://karma-runner.github.io/1.0/index.html" target="_blank" title="Karma unit test runner">karma</a>
with SystemJS as described in the [Testing](guide/testing) guide.
在[测试](guide/testing)指南中提到的 <a href="https://karma-runner.github.io/1.0/index.html" target="_blank" title="Karma测试运行器">karma</a> 测试运行器的脚本。
</td>
</tr>
@ -211,6 +269,10 @@ If you do, this page can help you understand their purpose.
See instructions in the optional
[_Deleting non-essential files_](guide/setup#non-essential "Setup: Deleting non-essential files") section.
*Do this only in the beginning to avoid accidentally deleting your own tests and git setup!*
这个列表中的文件在清理时可以删除它是原始的“快速起步”种子工程中的测试和git维护文件。
步骤参见可选的[删除非必要文件](guide/setup#non-essential "Setup: Deleting non-essential files")部分。
*只在最初做这件事以免不小心删除了你自己的测试文件和git配置*
</td>
</tr>
@ -225,6 +287,9 @@ If you do, this page can help you understand their purpose.
The open source MIT license to use this setup code in your application.
应用的搭建代码中用到的开源 MIT 许可证。
</td>
</tr>
@ -240,10 +305,17 @@ If you do, this page can help you understand their purpose.
Identifies `npm `package dependencies for the project.
为项目指定`npm`依赖包。
Contains command scripts for running the application,
running tests, and more. Enter `npm run` for a listing.
<a href="https://github.com/angular/quickstart/blob/master/README.md#npm-scripts"
target="_blank" title="npm scripts for Angular documentation samples">Read more</a> about them.
包含了一些命令脚本,用来运行应用、运行测试与其他。输入`npm run`来查看命令列表。
<a href="https://github.com/angular/quickstart/blob/master/README.md#npm-scripts"
target="_blank" title="Angular 文档例子的 npm 脚本">这里</a>阅读更多关于它们的说明。
</td>
</tr>
@ -260,6 +332,8 @@ If you do, this page can help you understand their purpose.
Configuration for the
<a href="http://www.protractortest.org/" target="_blank" title="Protractor: end-to-end testing for Angular">protractor</a>
_end-to-end_ (e2e) test runner.
<a href="http://www.protractortest.org/" target="_blank" title="Protractor: Angular 的端对端测试">protractor</a> *端对端* (e2e) 测试器运行器的配置。
</td>
</tr>
@ -275,6 +349,9 @@ If you do, this page can help you understand their purpose.
Instruction for using this git repository in your project.
Worth reading before deleting.
项目中使用这个 git 库的说明。
在删除前值得阅读。
</td>
</tr>
@ -290,6 +367,8 @@ If you do, this page can help you understand their purpose.
Global styles for the application. Initialized with an `<h1>` style for the QuickStart demo.
应用的全局样式。初始化后,有个为《快速起步》演示准备的`<h1>`样式。
</td>
</tr>
@ -305,12 +384,17 @@ If you do, this page can help you understand their purpose.
Tells the **SystemJS** module loader where to find modules
referenced in JavaScript `import` statements. For example:
**SystemJS** 模块加载器指定去哪儿查找在 JavaScript 的`import`语句中引用的模块。例如:
<code-example language="ts">
import { Component } from '@angular/core;
</code-example>
Don't touch this file unless you are fully versed in SystemJS configuration.
除非你完全理解 SystemJS 的配置,不要修改它。
</td>
</tr>
@ -327,6 +411,10 @@ If you do, this page can help you understand their purpose.
Optional extra SystemJS configuration.
A way to add SystemJS mappings, such as for appliation _barrels_,
without changing the original `system.config.js`.
可选的额外 SystemJS 配置。
是添加 SystemJS 映射的途径,例如在无需修改原始`systemjs.config.js`的情况下为应用映射*封装桶*。
</td>
</tr>
@ -342,6 +430,9 @@ If you do, this page can help you understand their purpose.
Tells the TypeScript compiler how to transpile TypeScript source files
into JavaScript files that run in all modern browsers.
为 TypeScript 编译器指定如何将 TypeScript 代码转换为 JavaScript 文件,用来在所有现代浏览器中运行。
</td>
</tr>
@ -358,8 +449,12 @@ If you do, this page can help you understand their purpose.
The `npm` installed TypeScript linter inspects your TypeScript code
and complains when you violate one of its rules.
This file defines linting rules favored by the
利用`npm`安装的 TypeScript 语法检查器 (linter) 检测 TypeScript 代码并在你违反它的规则时提示你。
This file defines linting rules favored by the
[Angular style guide](guide/style-guide) and by the authors of the documentation.
该文件定义了 [Angular 风格指南](guide/style-guide)与本文档站作者喜爱的语法检查规则。
</td>
</tr>

View File

@ -1,8 +1,8 @@
@title
Setup for local development
搭建本地开发环境
@intro
Install the Angular QuickStart seed for faster, more efficient development on your machine.
安装 Angular 《快速起步》种子,更快更有效地在本地开发应用
@description
@ -13,30 +13,55 @@ Install the Angular QuickStart seed for faster, more efficient development on yo
## Setup a local development environment
## 搭建本地开发环境
The <live-example name=quickstart>QuickStart live-coding</live-example> example is an Angular _playground_.
It's not where you'd develop a real application.
You [should develop locally](guide/setup#why-locally "Why develop locally") on your own machine ... and that's also how we think you should learn Angular.
<live-example name=quickstart>《快速起步》在线编程</live-example>例子是 Angular 的*游乐场*。
它不是开发真实应用的地方。
你应该在自己的电脑上[本地开发](guide/setup#why-locally "为什么在本地开发?")... 你也应该在本地环境学习 Angular。
Setting up a new project on your machine is quick and easy with the **QuickStart seed**,
maintained [on github](https://github.com/angular/quickstart "Install the github QuickStart repo").
利用 [github 上](https://github.com/angular/quickstart "安装 github 《快速起步》库")的**《快速起步》种子**在你的电脑上搭建一个新项目是很快很容易的。
Make sure you have [node and npm installed](guide/setup#install-prerequisites "What if you don't have node and npm?").
Then ...
确定你已经安装了 [node和npm](guide/setup#install-prerequisites "如果你没有node和npm"),然后:
1. Create a project folder (you can call it `quickstart` and rename it later).
新建项目目录(你可以暂时为其取名`quickstart`,以后再重命名)。
1. [Clone](guide/setup#clone "Clone it from github") or [download](guide/setup#download "download it from github") the **QuickStart seed** into your project folder.
[克隆](guide/setup#clone "从 github 克隆")或者[下载](guide/setup#download "从 github 下载")**《快速起步》种子**到你的项目目录。
1. Install [npm](guide/setup#install-prerequisites "What if you don't have node and npm?") packages.
安装 [npm](guide/setup#install-prerequisites "如果你没有 node和npm ") 包。
1. Run `npm start` to launch the sample application.
运行`npm start`来启动例子应用。
{@a clone}
### Clone
### 克隆
Perform the _clone-to-launch_ steps with these terminal commands.
运行下列命令来执行*克隆并启动*步骤。
<code-example language="sh" class="code-shell">
git clone https://github.com/angular/quickstart.git quickstart
@ -54,6 +79,8 @@ Perform the _clone-to-launch_ steps with these terminal commands.
`npm start` fails in _Bash for Windows_ which does not support networking to servers as of January, 2017.
在*Bash for Windows*中`npm start`可能会失败因为到2017-01为止它还不支持访问网络上的服务器。
</div>
@ -63,9 +90,15 @@ Perform the _clone-to-launch_ steps with these terminal commands.
### Download
### 下载
<a href="https://github.com/angular/quickstart/archive/master.zip" title="Download the QuickStart seed repository">Download the QuickStart seed</a>
and unzip it into your project folder. Then perform the remaining steps with these terminal commands.
<a href="https://github.com/angular/quickstart/archive/master.zip" title="下载《快速起步》种子库">下载《快速起步》种子</a>
并解压到你的项目目录中。然后执行下面的命令完成剩余步骤。
<code-example language="sh" class="code-shell">
cd quickstart
@ -82,6 +115,8 @@ and unzip it into your project folder. Then perform the remaining steps with the
`npm start` fails in _Bash for Windows_ which does not support networking to servers as of January, 2017.
在*Bash for Windows*中`npm start`可能会失败因为到2017-01为止它还不支持访问网络上的服务器。
</div>
@ -93,9 +128,14 @@ and unzip it into your project folder. Then perform the remaining steps with the
## Delete _non-essential_ files (optional)
## 删除*非必需*文件(可选)
You can quickly delete the _non-essential_ files that concern testing and QuickStart repository maintenance
(***including all git-related artifacts*** such as the `.git` folder and `.gitignore`!).
你可以快速删除一些涉及到测试和维护快速开始版本库的 *非必需* 文件
***包括所有git相关的文件***如 `.git` 文件夹和 `.gitignore`!)。
<div class="alert is-important">
@ -103,6 +143,8 @@ You can quickly delete the _non-essential_ files that concern testing and QuickS
Do this only in the beginning to avoid accidentally deleting your own tests and git setup!
请只在开始时执行此删除操作以防你自己的测试和git文件被意外删除
</div>
@ -110,8 +152,11 @@ Do this only in the beginning to avoid accidentally deleting your own tests and
Open a terminal window in the project folder and enter the following commands for your environment:
在项目目录下打开一个终端窗口,并根据你的操作系统执行以下命令:
### OS/X (bash)
<code-example language="sh" class="code-shell">
xargs rm -rf &lt; non-essential-files.osx.txt
rm src/app/*.spec*.ts
@ -123,6 +168,7 @@ Open a terminal window in the project folder and enter the following commands fo
### Windows
<code-example language="sh" class="code-shell">
for /f %i in (non-essential-files.txt) do del %i /F /S /Q
rd .git /s /q
@ -138,6 +184,8 @@ Open a terminal window in the project folder and enter the following commands fo
## What's in the QuickStart seed?
## 《快速起步》种子库里都有什么?
The **QuickStart seed** contains the same application as the QuickStart playground.
@ -145,6 +193,9 @@ But its true purpose is to provide a solid foundation for _local_ development.
Consequently, there are _many more files_ in the project folder on your machine,
most of which you can [learn about later](guide/setup-systemjs-anatomy "Setup Anatomy").
**《快速起步》种子** 包含了与《快速起步》游乐场一样的应用,但是,它真正的目的是提供坚实的*本地*开发基础。
所以你的电脑里的项目目录里面有*更多文件*,参见[搭建剖析](guide/setup-systemjs-anatomy "Setup Anatomy")。
{@a app-files}
@ -152,6 +203,8 @@ most of which you can [learn about later](guide/setup-systemjs-anatomy "Setup An
Focus on the following three TypeScript (`.ts`) files in the **`/src`** folder.
注意**`/src`**目录中以下三个 TypeScript (`.ts`) 文件:
<div class='filetree'>
@ -208,15 +261,25 @@ Focus on the following three TypeScript (`.ts`) files in the **`/src`** folder.
All guides and cookbooks have _at least these core files_.
Each file has a distinct purpose and evolves independently as the application grows.
所有指南和烹饪书都至少有*这几个核心文件*。每个文件都有独特的用途,并且随着应用的成长各自独立演变。
Files outside `src/` concern building, deploying, and testing your app.
They include configuration files and external dependencies.
`src/` 目录之外的文件为构建、部署和测试app相关的文件他们只包括配置文件和外部依赖。
Files inside `src/` "belong" to your app.
Add new Typescript, HTML and CSS files inside the `src/` directory, most of them inside `src/app`,
unless told to do otherwise.
`src/` 目录下的文件才“属于”你的app。
除非明确指出,否则教程中添加的 TypeScriptHTML和CSS文件都在`src/`目录下,
大多数在`src/app`目录中。
The following are all in `src/`
`src/`目录文件详情如下:
<style>
td, th {vertical-align: top}
@ -237,11 +300,27 @@ The following are all in `src/`
<tr>
<th>
File
<p>
File
</p>
<p>
文件
</p>
</th>
<th>
Purpose
<p>
Purpose
</p>
<p>
用途
</p>
</th>
</tr>
@ -258,6 +337,9 @@ The following are all in `src/`
Defines the same `AppComponent` as the one in the QuickStart playground.
It is the **root** component of what will become a tree of nested components
as the application evolves.
定义与《快速起步》游乐场同样的`AppComponent`。
它是**根**组件,随着应用的演变,它将变成一颗嵌套组件树。
</td>
</tr>
@ -271,9 +353,13 @@ The following are all in `src/`
<td>
Defines `AppModule`, the [root module](guide/appmodule "AppModule: the root module") that tells Angular how to assemble the application.
Right now it declares only the `AppComponent`.
Soon there will be more components to declare.
Defines `AppModule`, the [root module](guide/appmodule "AppModule: the root module") that tells Angular how to assemble the application.
Right now it declares only the `AppComponent`.
Soon there will be more components to declare.
定义`AppModule`[根模块](guide/appmodule "AppModule: 根模块")为 Angular 描述如何组装应用。
目前,它只声明了`AppComponent`。
不久,它将声明更多组件。
</td>
</tr>
@ -294,6 +380,10 @@ The following are all in `src/`
it's the only viable choice for a sample running in a _live-coding_ environment like Plunker.
You'll learn about alternative compiling and [deployment](guide/deployment) options later in the documentation.
使[即时 (JiT) 编译器](glossary#jit)用编译应用并且在浏览器中[启动](guide/appmodule#main "启动应用")并运行应用。
对于大多数项目的开发,这都是合理的选择。而且它是在像 Plunker 这样的*在线编程*环境中运行例子的唯一选择。
你将在本文档中学习其他编译和开发选择。
</td>
</tr>
@ -308,8 +398,12 @@ The following are all in `src/`
### Next Step
### 下一步
If you're new to Angular, we recommend staying on the [learning path](guide/learning-angular "Angular learning path").
如果你是 Angular 初学者,建议根据[学习路径](guide/learning-angular "Angular学习路径")学习。
</div>
@ -321,22 +415,38 @@ If you're new to Angular, we recommend staying on the [learning path](guide/lear
## Appendix: node and npm
## 附录node 与 npm
Node.js and npm are essential to modern web development with Angular and other platforms.
Node powers client development and build tools.
The _npm_ package manager, itself a _node_ application, installs JavaScript libraries.
Node.js 和 npm 对使用 Angular 和其他平台进行现代网络开发是至关重要的。
Node 驱动客户端开发和构建工具。
*npm* 包管理器本身是 *node* 应用,用于安装 JavaScript 库。
<a href="https://docs.npmjs.com/getting-started/installing-node" target="_blank" title="Installing Node.js and updating npm">
Get them now</a> if they're not already installed on your machine.
如果你的电脑没有安装它们,<a href="https://docs.npmjs.com/getting-started/installing-node" target="_blank" title="安装 Node.js 和更新 npm">
立刻安装它们</a>
**Verify that you are running node `v4.x.x` or higher and npm `3.x.x` or higher**
by running the commands `node -v` and `npm -v` in a terminal/console window.
Older versions produce errors.
在终端/控制器窗口运行命令`node -v`和`npm -v`,来**确认你运行的 node 是`v4.x.x`或更高npm 为`3.x.x`或更高。**
老版本会产生错误。
We recommend [nvm](https://github.com/creationix/nvm) for managing multiple versions of node and npm.
You may need [nvm](https://github.com/creationix/nvm) if you already have projects running on your machine that
use other versions of node and npm.
我们推荐使用 [nvm](https://github.com/creationix/nvm) 来管理多版本 node 和 npm。
如果你的电脑上已经有使用其他版本 node 和 npm 的项目,你可能需要 nvm。
{@a why-locally}
@ -344,27 +454,59 @@ use other versions of node and npm.
## Appendix: Why develop locally
## 附录:为何在本地开发
<live-example title="QuickStart Seed in Plunker">Live coding</live-example> in the browser is a great way to explore Angular.
在浏览器中<live-example title="QuickStart Seed in Plunker">在线编程</live-example>是很好的探索 Angular 的方法。
Links on almost every documentation page open completed samples in the browser.
You can play with the sample code, share your changes with friends, and download and run the code on your own machine.
几乎每章文档里面的链接都在浏览器中打开完整的例子。
你可以用这些代码做实验,或者与朋友共享你的修改,或者下载并在你自己的电脑上运行这些代码。
The [QuickStart](quickstart "Angular QuickStart Playground") shows just the `AppComponent` file.
It creates the equivalent of `app.module.ts` and `main.ts` internally _for the playground only_.
so the reader can discover Angular without distraction.
The other samples are based on the QuickStart seed.
[快速起步](quickstart "Angular 快速起步游乐场")仅仅展示了`AppComponent`文件。
它在内部创建了只为*游乐场*而准备的等价`app.module.ts`和`main.ts`。
所以读者可以在零干扰的情况下探索 Angular。
其他例子是基于 《快速起步》种子的。
As much fun as this is ...
虽然有这么多的乐趣,但是...
* you can't ship your app in plunker
你不能在 plunker 里面发布你的应用
* you aren't always online when writing code
编程时你不可能总是在线
* transpiling TypeScript in the browser is slow
在浏览器中编译 TypeScript 很慢
* the type support, refactoring, and code completion only work in your local IDE
只有本地 IDE 有类型支持、代码重构和代码自动完成
Use the <live-example title="QuickStart Seed in Plunker">live coding</live-example> environment as a _playground_,
a place to try the documentation samples and experiment on your own.
It's the perfect place to reproduce a bug when you want to
<a href="https://github.com/angular/angular.io/issues/new" target="_blank" title="File a documentation issue">file a documentation issue</a> or
<a href="https://github.com/angular/angular/issues/new" target="_blank" title="File an Angular issue">file an issue with Angular itself</a>.
For real development, we strongly recommend [developing locally](guide/setup#develop-locally).
<live-example title="QuickStart Seed in Plunker"><i>在线编程</i></live-example>环境当做*游乐场*,一个尝试文档例子和自己做实验的地方。
当你想要<a href="https://github.com/angular/angular.io/issues/new" target="_blank" title="提交关于文档的问题">提交关于文档的问题</a>或者
<a href="https://github.com/angular/angular/issues/new" target="_blank" title="提交关于 Angular 的问题">提交关于 Angular 自身的问题</a>时,
它是重现错误的完美地方。
For real development, we strongly recommend [developing locally](guide/setup#develop-locally).
对于现实项目开发,我们强烈推荐在[本地开发](guide/setup#develop-locally)。

View File

@ -1,8 +1,8 @@
@title
Structural Directives
结构型指令
@intro
Angular has a powerful template engine that lets us easily manipulate the DOM structure of our elements.
Angular 有一个强力的模板引擎它能让你轻松维护元素的DOM树结构。
@description
@ -19,26 +19,65 @@ Angular has a powerful template engine that lets us easily manipulate the DOM st
This guide looks at how Angular manipulates the DOM with **structural directives** and
how you can write your own structural directives to do the same thing.
在本章中我们将看看Angular如何用*结构型指令*操纵DOM树以及我们该如何写自己的结构型指令来完成同样的任务。
### Table of contents
### 目录
* [What are structural directives?](guide/structural-directives#definition)
[什么是结构型指令?](guide/structural-directives#definition)
* [*NgIf* case study](guide/structural-directives#ngIf)
[*NgIf* 案例](guide/structural-directives#ngIf)
* [The asterisk (*) prefix](guide/structural-directives#asterisk)
[星号(*)前缀](guide/structural-directives#asterisk)
* [Inside *NgFor*](guide/structural-directives#ngFor)
[*NgFor* 指令内幕](guide/structural-directives#ngFor)
* [microsyntax](guide/structural-directives#microsyntax)
[微语法](guide/structural-directives#microsyntax)
* [template input variables](guide/structural-directives#template-input-variable)
[模板输入变量](guide/structural-directives#template-input-variable)
* [one structural directive per element](guide/structural-directives#one-per-element)
[每个元素一个结构型指令](guide/structural-directives#one-per-element)
* [Inside the *NgSwitch* directives](guide/structural-directives#ngSwitch)
[*NgSwitch* 指令内幕](guide/structural-directives#ngSwitch)
* [Prefer the (*) prefix](guide/structural-directives#prefer-asterisk)
[优先使用(*)前缀](guide/structural-directives#prefer-asterisk)
* [The &lt;ng-template> element](guide/structural-directives#template)
[&lt;ng-template> 元素](guide/structural-directives#template)
* [Group sibling elements with &lt;ng-container&gt;](guide/structural-directives#ng-container)
[使用&lt;ng-container&gt;对兄弟元素进行分组](guide/structural-directives#ng-container)
* [Write a structural directive](guide/structural-directives#unless)
[写自己的结构型指令](guide/structural-directives#unless)
Try the <live-example></live-example>.
试试<live-example></live-example>
{@a definition}
@ -46,16 +85,27 @@ Try the <live-example></live-example>.
## What are structural directives?
## 什么是结构型指令?
Structural directives are responsible for HTML layout.
They shape or reshape the DOM's _structure_, typically by adding, removing, or manipulating
elements.
结构型指令的职责是HTML布局。
它们塑造或重塑DOM的结构比如添加、移除或维护这些元素。
As with other directives, you apply a structural directive to a _host element_.
The directive then does whatever it's supposed to do with that host element and its descendents.
像其它指令一样,你可以把结构型指令应用到一个*宿主元素*上。
然后它就可以对宿主元素及其子元素做点什么。
Structural directives are easy to recognize.
An asterisk (*) precedes the directive attribute name as in this example.
结构型指令非常容易识别。
在这个例子中,星号(*)被放在指令的属性名之前。
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif)" region="ngif">
@ -65,18 +115,27 @@ An asterisk (*) precedes the directive attribute name as in this example.
No brackets. No parentheses. Just `*ngIf` set to a string.
没有方括号,没有圆括号,只是把`*ngIf`设置为一个字符串。
You'll learn in this guide that the [asterisk (*) is a convenience notation](guide/structural-directives#asterisk)
and the string is a [_microsyntax_](guide/structural-directives#microsyntax) rather than the usual
and the string isa [_microsyntax_](guide/structural-directives#microsyntax) rather than the usual
[template expression](guide/template-syntax#template-expressions).
Angular desugars this notation into a marked-up `<ng-template>` that surrounds the
host element and its descendents.
Each structural directive does something different with that template.
在这个例子中,我们将学到[星号(*)这个简写方法](guide/structural-directives#asterisk),而这个字符串是一个[*微语法*](guide/structural-directives#microsyntax),而不是通常的[模板表达式](guide/template-syntax#template-expressions)。
Angular会解开这个语法糖变成一个`<ng-template>`标记,包裹着宿主元素及其子元素。
每个结构型指令都可以用这个模板做点不同的事情。
Three of the common, built-in structural directives&mdash;[NgIf](guide/template-syntax#ngIf),
[NgFor](guide/template-syntax#ngFor), and [NgSwitch...](guide/template-syntax#ngSwitch)&mdash;are
described in the [_Template Syntax_](guide/template-syntax) guide and seen in samples throughout the Angular documentation.
Here's an example of them in a template:
三个常用的内置结构型指令 —— [NgIf](guide/template-syntax#ngIf)、[NgFor](guide/template-syntax#ngFor)和[NgSwitch...](guide/template-syntax#ngSwitch)。
我们在[*模板语法*](guide/template-syntax)一章中讲过它并且在Angular文档的例子中到处都在用它。下面是模板中的例子
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (built-in)" region="built-in">
@ -87,6 +146,8 @@ Here's an example of them in a template:
This guide won't repeat how to _use_ them. But it does explain _how they work_
and how to [write your own](guide/structural-directives#unless) structural directive.
本章不会重复讲如何*使用*它们,而是解释它们的*工作原理*以及如何[写自己的结构型指令](guide/structural-directives#unless)。
<div class="callout is-helpful">
@ -98,17 +159,29 @@ and how to [write your own](guide/structural-directives#unless) structural direc
<header>
指令的拼写形式
</header>
Throughout this guide, you'll see a directive spelled in both _UpperCamelCase_ and _lowerCamelCase_.
Already you've seen `NgIf` and `ngIf`.
There's a reason. `NgIf` refers to the directive _class_;
`ngIf` refers to the directive's _attribute name_.
在本章中,我们将看到指令同时具有两种拼写形式*大驼峰`UpperCamelCase`和小驼峰`lowerCamelCase`,比如我们已经看过的`NgIf`和`ngIf`。
这里的原因在于,`NgIf`引用的是指令的*类名*,而`ngIf`引用的是指令的*属性名*。
A directive _class_ is spelled in _UpperCamelCase_ (`NgIf`).
A directive's _attribute name_ is spelled in _lowerCamelCase_ (`ngIf`).
The guide refers to the directive _class_ when talking about its properties and what the directive does.
The guide refers to the _attribute name_ when describing how
you apply the directive to an element in the HTML template.
指令的*类名*拼写成*大驼峰形式*`NgIf`),而它的*属性名*则拼写成*小驼峰形式*`ngIf`)。
本章会在谈论指令的属性和工作原理时引用指令的*类名*在描述如何在HTML模板中把该指令应用到元素时引用指令的*属性名*。
</div>
@ -121,17 +194,26 @@ you apply the directive to an element in the HTML template.
There are two other kinds of Angular directives, described extensively elsewhere:
(1)&nbsp;components and (2)&nbsp;attribute directives.
还有另外两种Angular指令在本开发指南的其它地方有讲解(1) 组件 (2) 属性型指令。
A *component* manages a region of HTML in the manner of a native HTML element.
Technically it's a directive with a template.
*组件*可以在原生HTML元素中管理一小片区域的HTML。从技术角度说它就是一个带模板的指令。
An [*attribute* directive](guide/attribute-directives) changes the appearance or behavior
of an element, component, or another directive.
For example, the built-in [`NgStyle`](guide/template-syntax#ngStyle) directive
changes several element styles at the same time.
[*属性型*指令](guide/attribute-directives)会改变某个元素、组件或其它指令的外观或行为。
比如,内置的[`NgStyle`](guide/template-syntax#ngStyle)指令可以同时修改元素的多个样式。
You can apply many _attribute_ directives to one host element.
You can [only apply one](guide/structural-directives#one-per-element) _structural_ directive to a host element.
我们可以在一个宿主元素上应用多个*属性型*指令,但[只能应用一个](guide/structural-directives#one-per-element)*结构型*指令。
</div>
@ -143,9 +225,13 @@ You can [only apply one](guide/structural-directives#one-per-element) _structura
## NgIf case study
## NgIf案例分析
`NgIf` is the simplest structural directive and the easiest to understand.
It takes a boolean expression and makes an entire chunk of the DOM appear or disappear.
我们重点看下`ngIf`。它是一个很好的结构型指令案例它接受一个布尔值并据此让一整块DOM树出现或消失。
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-true)" region="ngif-true">
@ -156,6 +242,9 @@ It takes a boolean expression and makes an entire chunk of the DOM appear or dis
The `ngIf` directive doesn't hide elements with CSS. It adds and removes them physically from the DOM.
Confirm that fact using browser developer tools to inspect the DOM.
`ngIf`指令并不是使用CSS来隐藏元素的。它会把这些元素从DOM中物理删除。
使用浏览器的开发者工具就可以确认这一点。
<figure class='image-display'>
<img src='assets/images/devguide/structural-directives/element-not-in-dom.png' alt="ngIf=false element not in DOM"></img>
@ -166,15 +255,24 @@ Confirm that fact using browser developer tools to inspect the DOM.
The top paragraph is in the DOM. The bottom, disused paragraph is not;
in its place is a comment about "bindings" (more about that [later](guide/structural-directives#asterisk)).
可以看到第一段文字出现在了DOM中而第二段则没有在第二段的位置上是一个关于“绑定”的注释[稍后](guide/structural-directives#asterisk)有更多讲解)。
When the condition is false, `NgIf` removes its host element from the DOM,
detaches it from DOM events (the attachments that it made),
detaches the component from Angular change detection, and destroys it.
The component and DOM nodes can be garbage-collected and free up memory.
当条件为假时,`NgIf`会从DOM中移除它的宿主元素取消它监听过的那些DOM事件从Angular变更检测中移除该组件并销毁它。
这些组件和DOM节点可以被当做垃圾收集起来并且释放它们占用的内存。
### Why *remove* rather than *hide*?
### 为什么*移除*而不是*隐藏*
A directive could hide the unwanted paragraph instead by setting its `display` style to `none`.
指令也可以通过把它的`display`风格设置为`none`而隐藏不需要的段落。
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (display-none)" region="display-none">
@ -184,6 +282,8 @@ A directive could hide the unwanted paragraph instead by setting its `display` s
While invisible, the element remains in the DOM.
当不可见时这个元素仍然留在DOM中。
<figure class='image-display'>
<img src='assets/images/devguide/structural-directives/element-display-in-dom.png' alt="hidden element still in DOM"></img>
@ -198,22 +298,39 @@ The component stays attached to its DOM element. It keeps listening to events.
Angular keeps checking for changes that could affect data bindings.
Whatever the component was doing, it keeps doing.
对于简单的段落,隐藏和移除之间的差异影响不大,但对于资源占用较多的组件是不一样的。当我们隐藏掉一个元素时,组件的行为还在继续 —— 它仍然附加在它所属的DOM元素上
它也仍在监听事件。Angular会继续检查哪些能影响数据绑定的变更。
组件原本要做的那些事情仍在继续。
Although invisible, the component&mdash;and all of its descendant components&mdash;tie up resources.
The performance and memory burden can be substantial, responsiveness can degrade, and the user sees nothing.
虽然不可见,组件及其各级子组件仍然占用着资源,而这些资源如果分配给别人可能会更有用。
在性能和内存方面的负担相当可观,响应度会降低,而用户却可能无法从中受益。
On the positive side, showing the element again is quick.
The component's previous state is preserved and ready to display.
The component doesn't re-initialize&mdash;an operation that could be expensive.
So hiding and showing is sometimes the right thing to do.
当然,从积极的一面看,重新显示这个元素会非常快。
组件以前的状态被保留着,并随时可以显示。
组件不用重新初始化 —— 该操作可能会比较昂贵。
这时候隐藏和显示就成了正确的选择。
But in the absence of a compelling reason to keep them around,
your preference should be to remove DOM elements that the user can't see
and recover the unused resources with a structural directive like `NgIf` .
但是除非有非常强烈的理由来保留它们否则我们更倾向于移除用户看不见的那些DOM元素并且使用`NgIf`这样的结构型指令来收回用不到的资源。
**These same considerations apply to every structural directive, whether built-in or custom.**
Before applying a structural directive, you might want to pause for a moment
to consider the consequences of adding and removing elements and of creating and destroying components.
**同样的考量也适用于每一个结构型指令,无论是内置的还是自定义的。**
我们应该提醒自己以及我们指令的使用者,来仔细考虑添加元素、移除元素以及创建和销毁组件的后果。
{@a asterisk}
@ -221,11 +338,17 @@ to consider the consequences of adding and removing elements and of creating and
## The asterisk (*) prefix
## 星号(*)前缀
Surely you noticed the asterisk (*) prefix to the directive name
and wondered why it is necessary and what it does.
你可能注意到了指令名的星号(*)前缀,并且困惑于为什么需要它以及它是做什么的。
Here is `*ngIf` displaying the hero's name if `hero` exists.
这里的`*ngIf`会在`hero`存在时显示英雄的名字。
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (asterisk)" region="asterisk">
@ -237,6 +360,10 @@ The asterisk is "syntactic sugar" for something a bit more complicated.
Internally, Angular desugars it in two stages.
First, it translates the `*ngIf="..."` into a template _attribute_, `template="ngIf ..."`,&nbsp; like this.
星号是一个用来简化更复杂语法的“语法糖”。
从内部实现来说Angular会分两个阶段解开这个语法糖。
首先,它把`*ngIf="..."`翻译成一个`template`*属性* `template="ngIf ..."`,代码如下:
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-template-attr)" region="ngif-template-attr">
@ -246,6 +373,8 @@ First, it translates the `*ngIf="..."` into a template _attribute_, `template="n
Then it translates the template _attribute_ into a `<ng-template>` _element_, wrapped around the host element, like this.
然后,它把这个`template`*属性*翻译成一个`<ng-template>`*元素*,并用它包裹宿主元素,代码如下:
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-template)" region="ngif-template">
@ -254,11 +383,19 @@ Then it translates the template _attribute_ into a `<ng-template>` _element_, wr
* The `*ngIf` directive moved to the `<ng-template>` element where it became a property binding,`[ngIf]`.
`*ngIf`指令被移到了`<ng-template>`元素上。在那里它变成了一个属性绑定`[ngIf]`。
* The rest of the `<div>`, including its class attribute, moved inside the `<ng-template>` element.
`<div>`上的其余部分,包括它的`class`属性在内,移到了内部的`<ng-template>`元素上。
None of these forms are actually rendered.
Only the finished product ends up in the DOM.
上述形式永远不会真的渲染出来。
只有最终产出的结果才会出现在DOM中。
<figure class='image-display'>
<img src='assets/images/devguide/structural-directives/hero-div-in-dom.png' alt="hero div in DOM"></img>
@ -269,8 +406,12 @@ Only the finished product ends up in the DOM.
Angular consumed the `<ng-template>` content during its actual rendering and
replaced the `<ng-template>` with a diagnostic comment.
Angular会在真正渲染的时候填充`<ng-template>`的内容,并且把`<ng-template>`替换为一个供诊断用的注释。
The [`NgFor`](guide/structural-directives#ngFor) and [`NgSwitch...`](guide/structural-directives#ngSwitch) directives follow the same pattern.
[`NgFor`](guide/structural-directives#ngFor)和[`NgSwitch...`](guide/structural-directives#ngSwitch)指令也都遵循同样的模式。
{@a ngFor}
@ -278,11 +419,17 @@ The [`NgFor`](guide/structural-directives#ngFor) and [`NgSwitch...`](guide/struc
## Inside _*ngFor_
## `*ngFor`内幕
Angular transforms the `*ngFor` in similar fashion from asterisk (*) syntax through
template _attribute_ to `<ng-template>` _element_.
Angular会把`*ngFor`用同样的方式把星号(*)语法的`template`*属性*转换成`<ng-template>`*元素*。
Here's a full-featured application of `NgFor`, written all three ways:
这里有一个`NgFor`的全特性应用,同时用了这三种写法:
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (inside-ngfor)" region="inside-ngfor">
@ -294,8 +441,14 @@ This is manifestly more complicated than `ngIf` and rightly so.
The `NgFor` directive has more features, both required and optional, than the `NgIf` shown in this guide.
At minimum `NgFor` needs a looping variable (`let hero`) and a list (`heroes`).
它明显比`ngIf`复杂得多,确实如此。
`NgFor`指令比本章展示过的`NgIf`具有更多的必选特性和可选特性。
至少`NgFor`会需要一个循环变量(`let hero`)和一个列表(`heroes`)。
You enable these features in the string assigned to `ngFor`, which you write in Angular's [microsyntax](guide/structural-directives#microsyntax).
我们可以通过把一个字符串赋值给`ngFor`来启用这些特性这个字符串使用Angular的[微语法](guide/structural-directives#microsyntax)。
<div class="alert is-helpful">
@ -305,6 +458,9 @@ Everything _outside_ the `ngFor` string stays with the host element
(the `<div>`) as it moves inside the `<ng-template>`.
In this example, the `[ngClass]="odd"` stays on the `<div>`.
`ngFor`字符串*之外*的每一样东西都会留在宿主元素(`<div>`)上,也就是说它移到了`<ng-template>`内部。
在这个例子中,`[ngClass]="odd"`留在了`<div>`上。
</div>
@ -315,39 +471,64 @@ In this example, the `[ngClass]="odd"` stays on the `<div>`.
### Microsyntax
### 微语法
The Angular microsyntax lets you configure a directive in a compact, friendly string.
The microsyntax parser translates that string into attributes on the `<ng-template>`:
Angular微语法能让我们通过简短的、友好的字符串来配置一个指令。
微语法解析器把这个字符串翻译成`<ng-template>`上的属性:
* The `let` keyword declares a [_template input variable_](guide/structural-directives#template-input-variable)
that you reference within the template. The input variables in this example are `hero`, `i`, and `odd`.
The parser translates `let hero`, `let i`, and `let odd` into variables named,
`let-hero`, `let-i`, and `let-odd`.
`let`关键字声明一个[模板输入变量](guide/structural-directives#template-input-variable),我们会在模板中引用它。本例子中,这个输入变量就是`hero`、`i`和`odd`。
解析器会把`let hero`、`let i`和`let odd`翻译成命名变量`let-hero`、`let-i`和`let-odd`。
* The microsyntax parser takes `of` and `trackby`, title-cases them (`of` -> `Of`, `trackBy` -> `TrackBy`),
and prefixes them with the directive's attribute name (`ngFor`), yielding the names `ngForOf` and `ngForTrackBy`.
Those are the names of two `NgFor` _input properties_ .
That's how the directive learns that the list is `heroes` and the track-by function is `trackById`.
微语法解析器接收`of`和`trackby`,把它们首字母大写(`of` -> `Of`, `trackBy` -> `TrackBy`
并且给它们加上指令的属性名(`ngFor`)前缀,最终生成的名字是`ngForOf`和`ngForTrackBy`。
还有两个`NgFor`的*输入属性*,指令据此了解到列表是`heroes`而track-by函数是`trackById`。
* As the `NgFor` directive loops through the list, it sets and resets properties of its own _context_ object.
These properties include `index` and `odd` and a special property named `$implicit`.
`NgFor`指令在列表上循环,每个循环中都会设置和重置它自己的*上下文*对象上的属性。
这些属性包括`index`和`odd`以及一个特殊的属性名`$implicit`(隐式变量)。
* The `let-i` and `let-odd` variables were defined as `let i=index` and `let odd=odd`.
Angular sets them to the current value of the context's `index` and `odd` properties.
`let-i`和`let-odd`变量是通过`let i=index`和`let odd=odd`来定义的。
Angular把它们设置为*上下文*对象中的`index`和`odd`属性的当前值。
* The context property for `let-hero` wasn't specified.
It's intended source is implicit.
Angular sets `let-hero` to the value of the context's `$implicit` property
which `NgFor` has initialized with the hero for the current iteration.
上下文中的属性`let-hero`没有指定过,实际上它来自一个隐式变量。
Angular会把`let-hero`设置为上下文对象中的`$implicit`属性,`NgFor`会用当前迭代中的英雄初始化它。
* The [API guide](api/common/index/NgFor-directive "API: NgFor")
describes additional `NgFor` directive properties and context properties.
[API参考手册](api/common/index/NgFor-directive "API: NgFor")中描述了`NgFor`指令的其它属性和上下文属性。
These microsyntax mechanisms are available to you when you write your own structural directives.
Studying the
[source code for `NgIf`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts "Source: NgIf")
and [`NgFor`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts "Source: NgFor")
is a great way to learn more.
这些微语法机制在你写自己的结构型指令时也同样有效,参考[`NgIf`的源码](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts "Source: NgIf")
和[`NgFor`的源码](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts "Source: NgFor") 可以学到更多。
{@a template-input-variable}
@ -358,10 +539,16 @@ is a great way to learn more.
### Template input variable
### 模板输入变量
A _template input variable_ is a variable whose value you can reference _within_ a single instance of the template.
There are several such variables in this example: `hero`, `i`, and `odd`.
All are preceded by the keyword `let`.
*模板输入变量*是这样一种变量,你可以*在单个实例的模板中*引用它的值。
这个例子中有好几个模板输入变量:`hero`、`i`和`odd`。
它们都是用`let`作为前导关键字。
A _template input variable_ is **_not_** the same as a
[template _reference_ variable](guide/template-syntax#ref-vars),
neither _semantically_ nor _syntactically_.
@ -431,9 +618,10 @@ The `NgSwitchDefault` displays its host element when no sibling `NgSwitchCase` m
The element to which you apply a directive is its _host_ element.
The `<happy-hero>` is the host element for the happy `*ngSwitchCase`.
The `<unknown-hero>` is the host element for the `*ngSwitchDefault`.
*Design thought*: minimize initialization effort and consider caching state in a
companion service.
*设计思路*:要最小化初始化的成本,并考虑把状态缓存在一个伴生的服务中。
</div>
@ -443,10 +631,36 @@ The `<unknown-hero>` is the host element for the `*ngSwitchDefault`.
As with other structural directives, the `NgSwitchCase` and `NgSwitchDefault`
can be desugared into the template _attribute_ form.
**These same considerations apply to every structural directive, whether built-in or custom.**
We should ask ourselves &mdash; and the users of our directives &mdash; to think carefully
about the consequences of adding and removing elements and of creating and destroying components.
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngswitch-template-attr)" region="ngswitch-template-attr">
**同样的考量也适用于每一个结构型指令,无论是内置的还是自定义的。**
我们应该提醒自己以及我们指令的使用者,来仔细考虑添加元素、移除元素以及创建和销毁组件的后果。
</code-example>
Let's see these dynamics at work. For fun, we'll stack the deck *against*
our recommendation and consider a component called `heavy-loader` that
***pretends*** to load a ton of data when initialized.
让我们在实践中看看这些变化。为了娱乐,我们设想在甲板上有个叫`heavy-loader`(重型起重机)的组件,它会***假装***在初始化时装载一吨数据。
We'll display two instances of the component. We toggle the visibility of the first one with CSS.
We toggle the second into and out of the DOM with `ngIf`.
我们将显示该组件的两个实例。我们使用CSS切换第一个实例的可见性用`ngIf`把第二个实例添加到DOM和将其移除。
<code-tabs>
<code-pane title="template (excerpt)" path="structural-directives/src/app/structural-directives.component.html" region="message-log">
</code-pane>
<code-pane title="heavy-loader.component.ts" path="structural-directives/src/app/heavy-loader.component.ts">
</code-pane>
</code-tabs>
@ -496,6 +710,9 @@ That's the fate of the middle "Hip!" in the phrase "Hip! Hip! Hooray!".
Angular erases the middle "Hip!", leaving the cheer a bit less enthusiastic.
借助内置的`ngOnInit`和`ngOnDestroy`[生命周期钩子](guide/lifecycle-hooks),我们同时记录了组件的创建或销毁过程。
下面是它的操作演示:
<figure class='image-display'>
<img src='assets/images/devguide/structural-directives/template-rendering.png' width="350" alt="template tag rendering"></img>
@ -677,9 +894,13 @@ that does the opposite of `NgIf`.
Creating a directive is similar to creating a component.
创建指令很像创建组件。
* Import the `Directive` decorator (instead of the `Component` decorator).
* Import the `Input`, `TemplateRef`, and `ViewContainerRef` symbols; you'll need them for _any_ structural directive.
导入`Directive`装饰器(而不再是`Component`)。
* Import the `Input`, `TemplateRef`, and `ViewContainerRef` symbols; you'll need them for _any_ structural directive .
* Apply the decorator to the directive class.
@ -783,8 +1004,11 @@ Then create some HTML to try it.
When the `condition` is falsy, the top (A) paragraph appears and the bottom (B) paragraph disappears.
When the `condition` is truthy, the top (A) paragraph is removed and the bottom (B) paragraph appears.
When the `condition` is falsy, the top (A) paragraph appears and the bottom (B) paragraph disappears.
When the`condition` is truthy, the top (A)paragraph is removed and the bottom (B)paragraph appears.
当`condition`为`false`时,顶部的段落就会显示出来,而底部的段落消失了。
当`condition`为`true`时,顶部的段落被移除了,而底部的段落显示了出来。
<figure class='image-display'>
@ -803,6 +1027,8 @@ You can both try and download the source code for this guide in the <live-exampl
Here is the source from the `src/app/` folder.
本章相关的代码如下:
<code-tabs>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
@title
TypeScript to JavaScript
从 TypeScript 到 JavaScript
@intro
Convert Angular TypeScript examples into ES6 and ES5 JavaScript.
把 Angular 的 TypeScript 范例转换为 ES6 和 ES5 JavaScript.
@description
@ -11,32 +11,72 @@ Anything you can do with Angular in _TypeScript_, you can also do
in JavaScript. Translating from one language to the other is mostly a
matter of changing the way you organize your code and access Angular APIs.
在 Angular 中_TypeScript_ 可以做的任何事,也可以用 JavaScript 实现。
将一种语言翻译成另一种语言,主要是改变了组织代码和访问 Angular API 的方式。
_TypeScript_ is a popular language option for Angular development.
Most code examples on the Internet as well as on this site are written in _TypeScript_.
This cookbook contains recipes for translating _TypeScript_
code examples to _ES6_ and to _ES5_ so that JavaScript developers
can read and write Angular apps in their preferred dialect.
_TypeScript_ 在 Angular 开发中比较流行。
互联网上和本网站中的大多数范例都是用 _TypeScript_ 写的。
{@a toc}
## Table of contents
## 目录
* [_TypeScript_ to _ES6_ to _ES5_](guide/ts-to-js#from-ts)<br>
[_TypeScript_ 到 _ES6_ 到 _ES5_](guide/ts-to-js#from-ts)<br>
* [Modularity: imports and exports](guide/ts-to-js#modularity)<br>
[模块化:导入和导出](guide/ts-to-js#modularity)<br>
* [Classes and Class Metadata](guide/ts-to-js#class-metadata)<br>
[类和类的元数据](guide/ts-to-js#class-metadata)<br>
* [_ES5_ DSL](guide/ts-to-js#dsl)<br>
[_ES5_ 领域专用语言](guide/ts-to-js#dsl)<br>
* [Interfaces](guide/ts-to-js#interfaces)<br>
[接口](guide/ts-to-js#interfaces)<br>
* [Input and Output Metadata](guide/ts-to-js#io-decorators)<br>
[输入和输出元数据](guide/ts-to-js#io-decorators)<br>
* [Dependency Injection](guide/ts-to-js#dependency-injection)<br>
[依赖注入](guide/ts-to-js#dependency-injection)<br>
* [Host Binding](guide/ts-to-js#host-binding)<br>
[宿主绑定](guide/ts-to-js#host-binding)<br>
* [View and Child Decorators](guide/ts-to-js#view-child-decorators)<br>
[视图和子组件装饰器](guide/ts-to-js#view-child-decorators)<br>
* [AOT compilation in _TypeScript_ Only](guide/ts-to-js#aot)<br>
[只用于 _TypeScript_ 的预编译](guide/ts-to-js#aot)<br>
**Run and compare the live <live-example name="cb-ts-to-js">TypeScript</live-example> and <live-example name="cb-ts-to-js" lang="js">JavaScript</live-example>
code shown in this cookbook.**
运行在线例子,比较 <live-example name="cb-ts-to-js">TypeScript</live-example> 版和 <live-example name="cb-ts-to-js" lang="js">JavaScript</live-example> 版的代码。
{@a from-ts}
@ -44,17 +84,31 @@ code shown in this cookbook.**
## _TypeScript_ to _ES6_ to _ES5_
##_TypeScript_ 到 _ES6_ 到 _ES5_
_TypeScript_
<a href="https://www.typescriptlang.org" target="_blank" title='"TypeScript is a typed, superset of JavaScript"'>is a typed superset of _ES6 JavaScript_</a>.
_ES6 JavaScript_ is a superset of _ES5 JavaScript_. _ES5_ is the kind of JavaScript that runs natively in all modern browsers.
The transformation of _TypeScript_ code all the way down to _ES5_ code can be seen as "shedding" features.
_TypeScript_ <a href="https://www.typescriptlang.org" target="_blank" title='"TypeScript 是类型化的 JavaScript 的超集"'>_ES6 JavaScript_ 类型化的超集</a>。_ES6 JavaScript_ 是 _ES5 JavaScript_ 的超集。_ES5_ 是可以在所有现代浏览器中运行的 JavaScript。
The downgrade progression is
降级的过程是
* _TypeScript_ to _ES6-with-decorators_
_TypeScript_ 降级到 _带装饰器的 ES6_
* _ES6-with-decorators_ to _ES6-without-decorators_ ("_plain ES6_")
_带装饰器的 ES6_ 降级到 _没有装饰器的 ES6_ (“_普通 ES6_”)
* _ES6-without-decorators_ to _ES5_
_没有装饰器的 ES6_ 降级到 _ES5_
When translating from _TypeScript_ to _ES6-with-decorators_, remove
[class property access modifiers](http://www.typescriptlang.org/docs/handbook/classes.html#public-private-and-protected-modifiers)
such as `public` and `private`.
@ -63,21 +117,32 @@ Remove most of the
such as `:string` and `:boolean`
but **keep the constructor parameter types which are used for dependency injection**.
_TypeScript_ 翻译到 _带装饰器的 ES6_ 时,移除了[类属性访问修饰符](http://www.typescriptlang.org/docs/handbook/classes.html#public-private-and-protected-modifiers),如`public`和`private`。
移除了大部分的[类型声明](https://www.typescriptlang.org/docs/handbook/basic-types.html),如`:string`和`:boolean`。
但**保留了用于依赖注入的构造函数参数类型**。
From _ES6-with-decorators_ to _plain ES6_, remove all
[decorators](https://www.typescriptlang.org/docs/handbook/decorators.html)
and the remaining types.
You must declare properties in the class constructor (`this.title = '...'`) rather than in the body of the class.
_带装饰器的 ES6_ 翻译到_普通 ES6_ 时,移除了所有的[装饰器](https://www.typescriptlang.org/docs/handbook/decorators.html)和剩下的类型。
必须在构造函数中声明属性(`this.title = '...'`),而不是在类的代码体中。
Finally, from _plain ES6_ to _ES5_, the main missing features are `import`
statements and `class` declarations.
最后_普通 ES6_ 翻译成 _ES5_,缺少的主要特性是`import`和`class`声明。
For _plain ES6_ transpilation you can _start_ with a setup similar to the
[_TypeScript_ quickstart](https://github.com/angular/quickstart) and adjust the application code accordingly.
Transpile with [Babel](https://babeljs.io/) using the `es2015` preset.
To use decorators and annotations with Babel, install the
[`angular2`](https://github.com/shuhei/babel-plugin-angular2-annotations) preset as well.
对_普通 ES6_ 的翻译,可以从类似 [_TypeScript_ 快速开始](https://github.com/angular/quickstart)的设置开始,
调整相应代码。然后用 [Babel](https://babeljs.io/) 进行转译,使用`es2015`预设值。
要在 Babel 中使用装饰器和注释,还需安装[`angular2`](https://github.com/shuhei/babel-plugin-angular2-annotations)预设值。
{@a modularity}
@ -86,14 +151,23 @@ To use decorators and annotations with Babel, install the
## Importing and Exporting
## 导入和导出
### Importing Angular Code
### 导入 Angular 代码
In both _TypeScript_ and _ES6_, you import Angular classes, functions, and other members with _ES6_ `import` statements.
_TypeScript__ES6_ 中,可以使用 _ES6_ `import`语句导入 Angular 类、函数和其它成员。
In _ES5_, you access the Angular entities of the [the Angular packages](glossary#scoped-package)
through the global `ng` object.
Anything you can import from `@angular` is a nested member of this `ng` object:
_ES5_ 中,通过全局`ng`对象访问 [Angular 包](glossary#scoped-package)中的 Angular 实体。
凡是可以从`@angular`导入的,都是该`ng`对象的嵌套成员。
<code-tabs>
@ -119,11 +193,19 @@ Anything you can import from `@angular` is a nested member of this `ng` object:
### Exporting Application Code
### 导出应用代码
Each file in a _TypeScript_ or _ES6_ Angular application constitutes an _ES6_ module.
When you want to make something available to other modules, you `export` it.
_ES5_ lacks native support for modules.
In an Angular _ES5_ application, you load each file manually by adding a `<script>` tag to `index.html`.
_TypeScript_ 或 _ES6_ Angular 应用中每个文件都构成一个 _ES6_ 模块。
当想要让某个东西对其它模块可用时,就`export`它。
_ES5_ lacks native support for modules.
In an Angular _ES5_ application, you load each file manually by adding a `<script>` tag to `index.html`.
_ES5_ 不支持模块。在 Angular _ES5_ 应用中,需要在`index.html`中添加`<script>`标签,手工加载每个文件。
<div class="alert is-important">
@ -132,6 +214,10 @@ In an Angular _ES5_ application, you load each file manually by adding a `<scrip
The order of `<script>` tags is often significant.
You must load a file that defines a public JavaScript entity before a file that references that entity.
`<script>`标签的顺序通常很重要。
必须在引用实体的文件之前,加载定义该公共 JavaScript 实体的文件。
</div>
@ -142,12 +228,22 @@ Then each code file "exports" public entities by attaching them to that namespac
You could factor a large application into several sub-namespaces
which leads to "exports" along the lines of `app.heroQueries.HeroComponent`.
Every _ES5_ file should wrap code in an
_ES5_ 中,最佳实践是,创建某种形式的模块化,避免污染全局作用域。
添加一个应用命名空间对象(如`app`)到全局的`document`。
接着,每个代码文件都通过附加到该命名空间来“导出”公共实体,例如,`app.HeroComponent`。
可以把一个大型应用中分解成多个子命名空间,可以象这样进行“导出”,`app.heroQueries.HeroComponent`
Every _ES5_ file should wrap code in an
[Immediately Invoked Function Expression (IIFE)](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression)
to limit unintentional leaking of private symbols into the global scope.
每个 _ES5_ 文件都应包裹在[立即调用函数表达式 (IIFE)](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression) 中,
防止把私有符号无意地泄漏到全局作用域。
Here is a `HeroComponent` as it might be defined and "exported" in each of the four language variants.
下面是`HeroComponent`定义和“导出”的四种不同语言变种。
<code-tabs>
@ -173,10 +269,16 @@ Here is a `HeroComponent` as it might be defined and "exported" in each of the f
### Importing Application Code
### 导入应用代码
In _TypeScript_ and _ES6_ apps, you `import` things that have been exported from other modules.
_TypeScript__ES6_ 应用中,可以导入 (`import`) 其它模块已导出的东西。
In _ES5_ you use the shared namespace object to access "exported" entities from other files.
_ES5_ 中,使用共享的命名空间对象访问其它文件“导出”的实体。
<code-tabs>
@ -209,6 +311,9 @@ Browserify in an Angular JavaScript project. In such a project, you would
use _CommonJS_ modules and the `require` function to load Angular framework code.
Then use `module.exports` and `require` to export and import application code.
还可以在 Angular JavaScript 项目中使用模块加载器,如 Webpack 或 Browserify。
在这样的项目中,使用 _CommonJS_ 模块和`require`函数来加载 Angular 框架代码。
用`module.exports`和`require`导入和导出应用代码。
</div>
@ -221,28 +326,48 @@ Then use `module.exports` and `require` to export and import application code.
## Classes and Class Metadata
## 类和类的元数据
### Classes
### 类
Most Angular _TypeScript_ and _ES6_ code is written as classes.
大多数 Angular _TypeScript__ES6_ 代码是写成了类。
Properties and method parameters of _TypeScript_ classes may be marked with the access modifiers
`private`, `internal`, and `public`.
Remove these modifiers when translating to JavaScript.
_TypeScript_ 类的属性和方法参数可以用访问修饰符`private`、`internal`和`public`标记。
当翻译成 JavaScript 时,移除这些修饰符。
Most type declarations (e.g, `:string` and `:boolean`) should be removed when translating to JavaScript.
When translating to _ES6-with-decorators_, ***do not remove types from constructor parameters!***
当翻译成 JavaScript 时,移除大多数类型声明(如,`:string`和`:boolean`)。
当翻译成_带装饰器的 ES6_ 时,***不移除构造函数参数类型!***
Look for types in _TypeScript_ property declarations.
In general it is better to initialize such properties with default values because
many browser JavaScript engines can generate more performant code.
When _TypeScript_ code follows this same advice, it can infer the property types
and there is nothing to remove during translation.
看一下 _TypeScript_ 属性声明中的类型。通常,最好用缺省值初始化这些属性,因为许多浏览器的 JavaScript
引擎可生成更高性能的代码。当 _TypeScript_ 代码遵循这一建议时,它可以推导出属性类型,翻译时就不需要移除任何内容。
In _ES6-without-decorators_, properties of classes must be assigned inside the constructor.
在_不带装饰器的 ES6_ 中,类的属性必须在构造函数中指定。
_ES5_ JavaScript has no classes.
Use the constructor function pattern instead, adding methods to the prototype.
_ES5_ JavaScript 没有类。
使用构造函数模式,把方法添加到 prototype 中。
<code-tabs>
@ -268,19 +393,30 @@ Use the constructor function pattern instead, adding methods to the prototype.
### Metadata
### 元数据
When writing in _TypeScript_ or _ES6-with-decorators_,
provide configuration and metadata by adorning a class with one or more *decorators*.
For example, you supply metadata to a component class by preceding its definition with a
[`@Component`](api/core/index/Component-decorator) decorator function whose
argument is an object literal with metadata properties.
当用 _TypeScript__带装饰器的 ES6_ 编写代码时,使用一个或多个*装饰器 (decorator)* 来修饰类,
提供配置和元数据。
In _plain ES6_, you provide metadata by attaching an `annotations` array to the _class_.
Each item in the array is a new instance of a metadata decorator created with a similar metadata object literal.
在_普通 ES6_ 中通过向_类_附加一个`annotations`数组来提供元数据。
In _ES5_, you also provide an `annotations` array but you attach it to the _constructor function_ rather than to a class.
在_ES5_中也是提供一个`annotations`数组但把它附加到_构造函数_而不是类。
See these variations side-by-side:
看一下这些变种:
<code-tabs>
@ -306,8 +442,13 @@ See these variations side-by-side:
***External Template file***
***外部模块文件***
A large component template is often kept in a separate template file.
大的组件模板通常是放在独立的文件中。
<code-example path="cb-ts-to-js/ts/src/app/hero-title.component.html" title="src/app/hero-title.component.html" linenums="false">
</code-example>
@ -316,6 +457,9 @@ A large component template is often kept in a separate template file.
The component (`HeroTitleComponent` in this case) then references the template file in its metadata `templateUrl` property:
接着,组件(这里是`HeroTitleComponent`)在它的元数据`templateUrl`属性中引用该模板文件:
<code-tabs>
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-title.component.ts" region="templateUrl">
@ -340,6 +484,8 @@ The component (`HeroTitleComponent` in this case) then references the template f
Note that both the _TypeScript_ and _ES6_ `templateUrl` properties identify the location of the template file _relative to the component module_.
注意_TypeScript_ 和两个_ES6_的`templateUrl`属性_相对于组件模块_来标识模板文件的位置。
{@a dsl}
@ -347,19 +493,32 @@ Note that both the _TypeScript_ and _ES6_ `templateUrl` properties identify the
## _ES5_ DSL
## _ES5_ 领域专用语言
This _ES5_ pattern of creating a constructor and annotating it with metadata is so common that Angular
provides a convenience API to make it a little more compact and locates the metadata above the constructor,
as you would if you wrote in _TypeScript_ or _ES6-with-decorators_.
创建构造函数并用元数据对它进行注释,是一个常见的 _ES5_ 模式Angular 提供了一套方便的 API使代码更简洁
并且元数据也刚好位于构造函数的上方,看起来就像 _TypeScript__带装饰器的 ES6_ 写的代码。
This _API_ (_Application Programming Interface_) is commonly known as the _ES5 DSL_ (_Domain Specific Language_).
这个 _API_ (_Application Programming Interface应用编程接口_) 通常称作 _ES5 DSL_ (_Domain Specific Language领域专用语言_)。
Set an application namespace property (e.g., `app.HeroDslComponent`) to the result of an `ng.core.Component` function call.
Pass the same metadata object to `ng.core.Component` as you did before.
Then chain a call to the `Class` method which takes an object defining the class constructor and instance methods.
把`ng.core.Component`函数调用的结果设置到应用命名空间属性,如`app.HeroDslComponent`。
向`ng.core.Component`传递与之前一样的元数据对象。
接着,在调用链上调用`Class`函数,它接收一个对象,其中定义了类的构造函数和实例方法。
Here is an example of the `HeroComponent`, re-written with the DSL,
next to the original _ES5_ version for comparison:
下例中的`HeroComponent`,用 DSL 进行了重写,跟原来的 _ES5_ 版本进行对比一下:
<code-tabs>
@ -385,11 +544,20 @@ next to the original _ES5_ version for comparison:
<header>
命名构造函数
</header>
A **named** constructor displays clearly in the console log
if the component throws a runtime error.
An **unnamed** constructor displays as an anonymous function (e.g., `class0`)
which is impossible to find in the source code.
如果组件抛出运行时异常,**命名**的构造函数在控制台日志中显示得更清楚。
**未命名**的构造函数显示为匿名函数(如,`class0`),不可能在源代码中找到它。
</div>
@ -397,10 +565,16 @@ which is impossible to find in the source code.
### Properties with getters and setters
### 具有 getter 和 setter 的属性
_TypeScript_ and _ES6_ support with getters and setters.
Here's an example of a read-only _TypeScript_ property with a getter
that prepares a toggle-button label for the next clicked state:
_TypeScript_ 和 _ES6_ 支持 getter 和 setter。
下面是 _TypeScript_ 只读属性的例子,它有一个 getter为下一次点击状态准备切换按钮的标签
<code-example path="cb-ts-to-js/ts/src/app/hero-queries.component.ts" region="defined-property" title="ts/src/app/hero-queries.component.ts" linenums="false">
</code-example>
@ -414,6 +588,11 @@ The _ES5 DSL_ does not support _defined properties_ directly
but you can still create them by extracting the "class" prototype and
adding the _defined property_ in raw JavaScript like this:
这个 _TypeScript_ "getter" 属性会翻译成 _ES5_ <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"
target="_blank" title="Defined Properties">已定义属性</a>
_ES5 DSL_ 不直接支持_已定义属性_你仍可提取“类”原型象下面这样添加_已定义属性_
<code-example path="cb-ts-to-js/js/src/app/hero-queries.component.js" region="defined-property" title="js/src/app/hero-queries.component.ts" linenums="false">
</code-example>
@ -421,9 +600,14 @@ adding the _defined property_ in raw JavaScript like this:
### DSL for other classes
### 用于其它类的 DSL
There are similar DSLs for other decorated classes.
You can define a directive with `ng.core.Directive`:
其它被装饰的类也有类似的DSL可以用`ng.core.Directive`定义指令:
<code-example>
app.MyDirective = ng.core.Directive({
@ -437,6 +621,9 @@ You can define a directive with `ng.core.Directive`:
and a pipe with `ng.core.Pipe`:
用`ng.core.Pipe`添加一个管道:
<code-example>
app.MyPipe = ng.core.Pipe({
name: 'myPipe'
@ -454,15 +641,25 @@ and a pipe with `ng.core.Pipe`:
## Interfaces
## 接口
A _TypeScript_ interface helps ensure that a class implements the interface's members correctly.
We strongly recommend Angular interfaces where appropriate.
For example, the component class that implements the `ngOnInit` lifecycle hook method
should implement the `OnInit` interface.
_TypeScript_用于确保一个类正确地实现了接口成员。
在适当的地方,我们强烈推荐使用 Angular 接口。
例如,实现了`ngOnInit`生命周期钩子方法的组件类应实现`OnInit`接口。
_TypeScript_ interfaces exist for developer convenience and are not used by Angular at runtime.
They have no physical manifestation in the generated JavaScript code.
Just implement the methods and ignore interfaces when translating code samples from _TypeScript_ to JavaScript.
_TypeScript_ 接口只是为了方便开发人员Angular 在运行时并不使用它。
它们在生成的 JavaScript中并不存在。
当从 _TypeScript_ 翻译成 JavaScript 时,只保留了实现方法,而忽略接口。
<code-tabs>
@ -496,21 +693,37 @@ Just implement the methods and ignore interfaces when translating code samples f
## Input and Output Metadata
## 输入和输出元数据
### Input and Output Decorators
### 输入和输出装饰器
In _TypeScript_ and _ES6-with-decorators_, you often add metadata to class _properties_ with _property decorators_.
For example, you apply [`@Input` and `@Output` property decorators](guide/template-syntax#inputs-outputs)
to public class properties that will be the target of data binding expressions in parent components.
_TypeScript__带装饰器的 ES6_经常会用_属性装饰器_往类的_属性_上添加元数据。
例如,向公共类属性添加[`@Input`和`@Output`属性装饰器](guide/template-syntax#inputs-outputs)
会使这些属性成为父组件绑定表达式的目标。
There is no equivalent of a property decorator in _ES5_ or _plain ES6_.
Fortunately, every property decorator has an equivalent representation in a class decorator metadata property.
A _TypeScript_ `@Input` property decorator can be represented by an item in the `Component` metadata's `inputs` array.
_ES5__普通 ES6_ 中,没有等价的属性装饰器。
幸运的是,每个属性装饰器在类的装饰器元数据属性中有等价的表示形式。
_TypeScript_ 的`@Input`属性装饰器可以表示为`Component`元数据的`inputs`数组中的一项。
You already know how to add `Component` or `Directive` class metadata in _any_ JavaScript dialect so
there's nothing fundamentally new about adding another property.
But note that what would have been _separate_ `@Input` and `@Output` property decorators for each class property are
combined in the metadata `inputs` and `outputs` _arrays_.
你已经知道如何用_任意的_ JavaScript 方言添加`Component` 或 `Directive`元数据,
所以添加另一个属性也没什么新鲜的。
但要注意的是用于每个类属性的那些_分离_的`@Input`和`@Output`属性装饰器,都合并到了`inputs`和`outputs`_数组_中。
<code-tabs>
@ -543,45 +756,83 @@ differs from the corresponding class property name (`notOkMsg`).
That's OK but you must tell Angular about it so that it can map an external binding of `cancelMsg`
to the component's `notOkMsg` property.
In _TypeScript_ and _ES6-with-decorators_,
上例中,其中一个面向公共的绑定名 (`cancelMsg`),不同于相应的类属性名 (`notOkMsg`)。
这样做没有问题,但必须把它告诉 Angular这样 Angular 才能把`cancelMsg`的外部绑定映射到组件的`notOkMsg`属性。
In _TypeScript_ and _ES6-with-decorators_,
you specify the special binding name in the argument to the property decorator.
_TypeScript__带装饰器的 ES6_ 中,在属性装饰器的参数中指定特定的绑定名。
In _ES5_ and _plain ES6_ code, convey this pairing with the `propertyName: bindingName` syntax in the class metadata.
_ES5__普通 ES6_ 中,用`propertyName: bindingName`语法表示在类的元数据中。
## Dependency Injection
## 依赖注入
Angular relies heavily on [Dependency Injection](guide/dependency-injection) to provide services to the objects it creates.
When Angular creates a new component, directive, pipe or another service,
it sets the class constructor parameters to instances of services provided by an _Injector_.
Angular 严重依赖[依赖注入](guide/dependency-injection)来为它创建的对象提供服务。
当 Angular 创建一个新组件、指令、管道或其它服务时,
它把_注入器_提供的服务的实例传递给类的构造函数参数。
The developer must tell Angular what to inject into each parameter.
开发人员必须告诉 Angular 向每个参数中注入什么。
### Injection by Class Type
### 按类的类型注入
The easiest and most popular technique in _TypeScript_ and _ES6-with-decorators_ is to set the constructor parameter type
to the class associated with the service to inject.
to the class associated with the service to inject.
_TypeScript__带装饰器的 ES6_ 中,最简单和流行的技术是把构造函数参数的类型设置为待注入服务的类。
The _TypeScript_ transpiler writes parameter type information into the generated JavaScript.
Angular reads that information at runtime and locates the corresponding service in the appropriate _Injector_..
The _ES6-with-decorators_ transpiler does essentially the same thing using the same parameter-typing syntax.
_TypeScript_ 转译器把参数类型信息写进生成的 JavaScript。
Angular 在运行时读取该信息并在适当的_注入器_中定位相应的服务。
_带装饰器的 ES6_ 转译器本质上也使用同样的参数类型语法,做同样的工作。
_ES5_ and _plain ES6_ lack types so you must identify "injectables" by attaching a **`parameters`** array to the constructor function.
Each item in the array specifies the service's injection token.
_ES5_ 或 _普通 ES6_ 缺少类型,必须向构造函数附加**`parameters`**数组来标识“可注入对象”。
数组中的每一项指定一个服务的注入令牌。
As with _TypeScript_ the most popular token is a class,
or rather a _constructor function_ that represents a class in _ES5_ and _plain ES6_.
The format of the `parameters` array varies:
_TypeScript_ 中最常用的令牌是类而_ES5_ 和 _普通 ES6_ 使用_构造函数_表示一个类。
因此,`parameters`数组会有所不同:
* _plain ES6_ &mdash; nest each constructor function in a sub-array.
_普通 ES6_ &mdash; 函数构造嵌套在一个子数组中。
* _ES5_ &mdash; simply list the constructor functions.
_ES5_ &mdash; 简单列出构造函数。
When writing with _ES5 DSL_, set the `Class.constructor` property to
an array whose first parameters are the injectable constructor functions and whose
last parameter is the class constructor itself.
This format should be familiar to AngularJS developers.
当用 _ES5 DSL_ 时,把`Class.constructor`属性设置为一个数组,它的前面的参数是
注入的服务,最后一个参数是类构造函数本身。
AngularJS 的开发人员对这种形式应该很熟悉。
<code-tabs>
@ -611,22 +862,39 @@ This format should be familiar to AngularJS developers.
### Injection with the @Inject decorator
### 用 @Inject 装饰器注入
Sometimes the dependency injection token isn't a class or constructor function.
有时,依赖注入的令牌不是类或构造函数。
In _TypeScript_ and _ES6-with-decorators_, you precede the class constructor parameter
by calling the `@Inject()` decorator with the injection token.
In the following example, the token is the string `'heroName'`.
_TypeScript__带装饰器的 ES6_ 中,可以在类的构造函数参数前调用`@Inject()`装饰器来指定注入令牌。
在这个例子中,这个令牌是字符串`'heroName'`
The other JavaScript dialects add a `parameters` array to the class contructor function.
Each item constains a new instance of `Inject`:
其它 JavaScript 方言是通过向类的构造函数添加`parameters`数组。
其中的每一项是`Inject`的实例。
* _plain ES6_ &mdash; each item is a new instance of `Inject(token)` in a sub-array.
_普通 ES6_ &mdash; 每一项是嵌套在一个子数组中的`Inject(token)`的实例。
* _ES5_ &mdash; simply list the string tokens.
_ES5_ &mdash; 简单列出字符串令牌。
When writing with _ES5 DSL_, set the `Class.constructor` property to a function definition
array as before. Create a new instance of `ng.core.Inject(token)` for each parameter.
当用 _ES5 DSL_ 时,象前面那样把`Class.constructor`属性设置为函数定义数组。
为每个参数创建一个`ng.core.Inject(token)`。
<code-tabs>
@ -656,25 +924,52 @@ array as before. Create a new instance of `ng.core.Inject(token)` for each param
### Additional Injection Decorators
### 其它注入装饰器
You can qualify injection behavior with injection decorators from `@angular/core`.
可以使用`@angular/core`中的注入装饰器来限定注入行为。
In _TypeScript_ and _ES6-with-decorators_,
you precede the constructor parameters with injection qualifiers such as:
_TypeScript__带装饰器的 ES6_ 中,可以将下列注入限定符加在构造函数参数前面:
* [`@Optional`](api/core/index/Optional-decorator) sets the parameter to `null` if the service is missing
[`@Optional`](api/core/index/Optional-decorator) 如果找不到服务,设置参数为`null`
* [`@Attribute`](api/core/index/Attribute-interface) to inject a host element attribute value
[`@Attribute`](api/core/index/Attribute-interface) 注入宿主元素属性值
* [`@ContentChild`](api/core/index/ContentChild-decorator) to inject a content child
[`@ContentChild`](api/core/index/ContentChild-decorator) 注入内容子组件
* [`@ViewChild`](api/core/index/ViewChild-decorator) to inject a view child
[`@ViewChild`](api/core/index/ViewChild-decorator) 注入视图子组件
* [`@Host`](api/core/index/Host-decorator) to inject a service in this component or its host
[`@Host`](api/core/index/Host-decorator) 注入本组件或它宿主中的服务
* [`@SkipSelf`](api/core/index/SkipSelf-decorator) to inject a service provided in an ancestor of this component
[`@SkipSelf`](api/core/index/SkipSelf-decorator) 注入本组件祖先中提供的服务
In _plain ES6_ and _ES5_, create an instance of the equivalent injection qualifier in a nested array within the `parameters` array.
For example, you'd write `new Optional()` in _plain ES6_ and `new ng.core.Optional()` in _ES5_.
在_ES5_ 或 _普通 ES6_ 中,通过在`parameters`数组中创建一个嵌套数组,创建等价的注入限定符实例。
When writing with _ES5 DSL_, set the `Class.constructor` property to a function definition
array as before. Use a nested array to define a parameter's complete injection specification.
当用 _ES5 DSL_ 时,象前面那样把`Class.constructor`属性设置为函数定义数组。
用嵌套数组来定义参数完整的注入规格说明。
<code-tabs>
@ -711,6 +1006,11 @@ Without `Optional`, Angular would raise an error.
With `Optional`, Angular sets the constructor parameter to `null`
and the component displays the title without a prefix.
上例中,`'titlePrefix'`令牌没有提供商。
如果没有`Optional`Angular 将抛出错误。
加上`Optional`Angular 将构造函数参数设置为`null`
组件显示没有前缀的标题。
</div>
@ -721,11 +1021,19 @@ and the component displays the title without a prefix.
## Host Binding
## 宿主绑定
Angular supports bindings to properties and events of the _host element_ which is the
element whose tag matches the component selector.
Angular 支持绑定到_宿主元素_的属性和事件
宿主元素是那些标签匹配组件选择器的元素。
### Host Decorators
### 宿主装饰器
In _TypeScript_ and _ES6-with-decorators_, you can use host property decorators to bind a host
element to a component or directive.
The [`@HostBinding`](api/core/index/HostBinding-interface) decorator
@ -733,15 +1041,28 @@ binds host element properties to component data properties.
The [`@HostListener`](api/core/index/HostListener-interface) decorator binds
host element events to component event handlers.
_TypeScript__带装饰器的 ES6_ 中,可以使用宿主属性装饰器把宿主元素绑定到组件或指令。
[`@HostBinding`](api/core/index/HostBinding-interface)装饰器把宿主元素属性绑定到组件数据属性。
[`@HostListener`](api/core/index/HostListener-interface)装饰器把宿主元素事件绑定到组件事件处理器。
In _plain ES6_ or _ES5_, add a `host` attribute to the component metadata to achieve the
same effect as `@HostBinding` and `@HostListener`.
same effect as `@HostBinding` and `@HostListener`.
在_ES5_ 或 _普通 ES6_ 中,向组件元数据添加`host`属性可以获得同样的效果。
The `host` value is an object whose properties are host property and listener bindings:
`host`的值是一个对象,它的属性是宿主属性和监听器绑定:
* Each key follows regular Angular binding syntax: `[property]` for host bindings
or `(event)` for host listeners.
每个键遵循 Angular 绑定语法:`[property]`用于宿主绑定,`(event)`用于宿主监听器。
* Each value identifies the corresponding component property or method.
每个值标识相应的组件属性或方法。
<code-tabs>
@ -770,14 +1091,24 @@ The `host` value is an object whose properties are host property and listener b
### Host Metadata
### 宿主元数据
Some developers prefer to specify host properties and listeners
in the component metadata.
They'd _rather_ do it the way you _must_ do it _ES5_ and _plain ES6_.
一些开发人员更喜欢在组件元数据中指定宿主属性和监听器。
它们宁愿采用这种方式,也是 _ES5__普通 ES6_ 中必须采用的方式。
The following re-implementation of the `HeroComponent` reminds us that _any property metadata decorator_
can be expressed as component or directive metadata in both _TypeScript_ and _ES6-with-decorators_.
These particular _TypeScript_ and _ES6_ code snippets happen to be identical.
下面重新实现了`HeroComponent`,它提醒我们,在 _TypeScript__带装饰器的 ES6_ 中,
_任何属性元数据装饰器_都可以表示为组件或指令元数据。
<code-tabs>
<code-pane title="TypeScript" path="cb-ts-to-js/ts/src/app/hero-host-meta.component.ts">
@ -798,8 +1129,12 @@ These particular _TypeScript_ and _ES6_ code snippets happen to be identical.
### View and Child Decorators
### 视图和子组件装饰器
Several _property_ decorators query a component's nested view and content components.
有几个_属性_装饰器可用于查询组件的嵌套视图和内容组件。
<div class="l-sub-section">
@ -807,8 +1142,14 @@ Several _property_ decorators query a component's nested view and content compon
_View_ children are associated with element tags that appear _within_ the component's template.
_视图_子组件与出现在组件模板_内_的元素标签相关联。
_Content_ children are associated with elements that appear _between_ the component's element tags;
they are projected into an `<ng-content>` slot in the component's template.
they are projected into an `<ng-content>` slot in the component's template.
_内容_子组件与出现在组件元素标签_之间_的那些元素相关联
它们被投影到组件模板的`<ng-content>`中。
</div>
@ -819,12 +1160,24 @@ The [`@ViewChild`](api/core/index/ViewChild-decorator) and
allow a component to query instances of other components that are used in
its view.
[`@ViewChild`](api/core/index/ViewChild-decorator) 和
[`@ViewChildren`](api/core/index/ViewChildren-decorator)
属性装饰器允许组件查询位于其视图中的其它组件的实例。
In _ES5_ and _ES6_, you access a component's view children by adding a `queries` property to the component metadata.
The `queries` property value is a hash map.
_ES5__ES6_ 中,通过向组件元数据添加`queries`属性来访问组件的视图子组件。
`queries`属性是一个映射表。
* each _key_ is the name of a component property that will hold the view child or children.
每个_键_是用来保存视图子组件的组件属性名。
* each _value_ is a new instance of either `ViewChild` or `ViewChildren`.
每个_值_是`ViewChild`或`ViewChildren`的实例。
<code-tabs>
@ -853,9 +1206,16 @@ The [`@ContentChild`](api/core/index/ContentChild-decorator) and
allow a component to query instances of other components that have been projected
into its view from elsewhere.
[`@ContentChild`](api/core/index/ContentChild-decorator) 和
[`@ContentChildren`](api/core/index/ContentChildren-decorator)
装饰器允许组件查询从其它地方投影到视图里的其它组件的实例。
They can be added in the same way as [`@ViewChild`](api/core/index/ViewChild-decorator) and
[`@ViewChildren`](api/core/index/ViewChildren-decorator).
添加它们的方式与[`@ViewChild`](api/core/index/ViewChild-decorator) 和
[`@ViewChildren`](api/core/index/ViewChildren-decorator) 相同。
<code-tabs>
@ -886,6 +1246,9 @@ They can be added in the same way as [`@ViewChild`](api/core/index/ViewChild-dec
In _TypeScript_ and _ES6-with-decorators_ you can also use the `queries` metadata
instead of the `@ViewChild` and `@ContentChild` property decorators.
_TypeScript__带装饰器的 ES6_ 中,还可以使用`queries`元数据代替
`@ViewChild``@ContentChild`属性装饰器。
</div>
@ -897,8 +1260,15 @@ instead of the `@ViewChild` and `@ContentChild` property decorators.
## AOT Compilation in _TypeScript_ only
## 只用于 _TypeScript_ 的预编译
Angular offers two modes of template compilation, JIT (_Just-in-Time_) and
[AOT (_Ahead-of-Time_)](guide/aot-compiler).
Currently the AOT compiler only works with _TypeScript_ applications because, in part, it generates
_TypeScript_ files as an intermediate result.
**AOT is not an option for pure JavaScript applications** at this time.
**AOT is not an option for pure JavaScript applications** at this time.
Angular 模板编译有两种方式JiT (_Just-in-Time即时编译_) 和
[AoT (_Ahead-of-Time预编译_)](guide/aot-compiler)。
目前,预编译只能用于 _TypeScript_ 应用,因为(部分原因)它生成的中间结果是 _TypeScript_ 文件。
当前,**预编译不能用于纯 JavaScript 应用**。

View File

@ -1,8 +1,8 @@
@title
TypeScript Configuration
TypeScript 配置
@intro
TypeScript configuration for Angular developers.
Angular 开发者的 TypeScript 配置
@description
@ -10,14 +10,26 @@ TypeScript configuration for Angular developers.
TypeScript is a primary language for Angular application development.
It is a superset of JavaScript with design-time support for type safety and tooling.
Browsers can't execute TypeScript directly. Typescript must be "transpiled" into JavaScript using the *tsc* compiler,
TypeScript是Angular应用开发中使用的主语言。
它是JavaScript的“方言”之一为类型安全和工具化而做了设计期支持。
Browsers can't execute TypeScript directly. Typescript must be "transpiled" into JavaScript using the *tsc* compiler
which requires some configuration.
浏览器不能直接执行TypeScript。它得先用*tsc*编译器转译(transpile)成JavaScript而且编译器需要进行一些配置。
This page covers some aspects of TypeScript configuration and the TypeScript environment
that are important to Angular developers, including details about the following files:
* [tsconfig.json](guide/typescript-configuration#tsconfig)&mdash;TypeScript compiler configuration.
* [typings](guide/typescript-configuration#typings)&mdash;TypesScript declaration files.
本页面会覆盖TypeScript配置与环境的某些方面这些对Angular开发者是很重要的。具体来说包括下列文件
* [tsconfig.json](guide/typescript-configuration#tsconfig) &mdash; TypeScript compiler configuration.
[tsconfig.json](guide/typescript-configuration#tsconfig) - TypeScript编译器配置。
* [typings](guide/typescript-configuration#typings) &mdash; TypesScript declaration files.
[typings](guide/typescript-configuration#typings) - TypesScript类型声明文件。
{@a tsconfig}
@ -25,9 +37,13 @@ that are important to Angular developers, including details about the following
## *tsconfig.json*
Typically, you add a TypeScript configuration file called `tsconfig.json` to your project to
guide the compiler as it generates JavaScript files.
我们通常会往项目中加入一个TypeScript配置文件(`tsconfig.json`)来指导编译器如何生成JavaScript文件。
<div class="l-sub-section">
@ -35,40 +51,72 @@ guide the compiler as it generates JavaScript files.
For details about `tsconfig.json`, see the official
[TypeScript wiki](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
要了解关于`tsconfig.json`的详情,请参阅官方提供的
[TypeScript wiki](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html)。
</div>
The [Setup](guide/setup) guide uses the following `tsconfig.json`:
我们在[搭建本地开发环境](guide/setup)中创建过如下的`tsconfig.json`
This file contains options and flags that are essential for Angular applications.
该文件中的选项和标志是写Angular应用程序的基础。
{@a noImplicitAny}
### *noImplicitAny* and *suppressImplicitAnyIndexErrors*
### *noImplicitAny*与*suppressImplicitAnyIndexErrors*
TypeScript developers disagree about whether the `noImplicitAny` flag should be `true` or `false`.
There is no correct answer and you can change the flag later.
But your choice now can make a difference in larger projects, so it merits discussion.
TypeScript开发者们在`noImplicitAny`标志应该是`true`还是`false`上存在分歧。
这没有标准答案,我们以后还可以修改这个标志。
但是我们的选择会在大项目中产生显著差异,所以它值得讨论一番。
When the `noImplicitAny` flag is `false` (the default), and if
the compiler cannot infer the variable type based on how it's used,
the compiler silently defaults the type to `any`. That's what is meant by *implicit `any`*.
当`noImplicitAny`标志是`false`(默认值)时,
如果编译器无法根据变量的用途推断出变量的类型,它就会悄悄的把变量类型默认为`any`。这就是*隐式`any`*的含义。
The documentation setup sets the `noImplicitAny` flag to `true`.
本文档在环境搭建时将`noImplicitAny`标志设置为`true`。
When the `noImplicitAny` flag is `true` and the TypeScript compiler cannot infer
the type, it still generates the JavaScript files, but it also **reports an error**.
Many seasoned developers prefer this stricter setting because type checking catches more
unintentional errors at compile time.
当`noImplicitAny`标志是`true`并且TypeScript编译器无法推断出类型时它仍然会生成JavaScript文件。
但是它也会**报告一个错误**。
很多饱经沧桑的程序员更喜欢这种严格的设置,因为类型检查能在编译期间捕获更多意外错误。
You can set a variable's type to `any` even when the `noImplicitAny` flag is `true`.
即使`noImplicitAny`标志被设置成了`true`,你也可以把变量的类型设置为`any`。
When the `noImplicitAny` flag is `true`, you may get *implicit index errors* as well.
Most developers feel that *this particular error* is more annoying than helpful.
You can suppress them with the following additional flag:
如果我们把`noImplicitAny`标志设置为了`true`,我们可能会得到*隐式索引错*。
大多数程序员可能觉得*这种错误*是个烦恼而不是助力。
我们可以使用另一个标志来禁止它们。
<code-example format=".">
"suppressImplicitAnyIndexErrors":true
@ -78,38 +126,62 @@ You can suppress them with the following additional flag:
The documentation setup sets this flag to `true` as well.
本文档在环境搭建时将`noImplicitAny`标志设置为`true`。
{@a typings}
## TypeScript Typings
## TypeScript类型定义(typings)
Many JavaScript libraries, such as jQuery, the Jasmine testing library, and Angular,
extend the JavaScript environment with features and syntax
that the TypeScript compiler doesn't recognize natively.
When the compiler doesn't recognize something, it throws an error.
When the compiler doesn't recognize something, it throws an error.
很多JavaScript库比如jQuery、Jasmine测试库和Angular会通过新的特性和语法来扩展JavaScript环境。
而TypeScript编译器并不能原生的识别它们。
当编译器不能识别时,它就会抛出一个错误。
Use [TypeScript type definition files](https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html)&mdash;`d.ts files`&mdash;to tell the compiler about the libraries you load.
我们可以使用[TypeScript类型定义文件](https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html)
—— `.d.ts`文件 —— 来告诉编译器要加载的库的类型定义。
TypeScript-aware editors leverage these same definition files to display type information about library features.
TypeScript敏感的编辑器借助这些定义文件来显示这些库中各个特性的类型定义。
Many libraries include definition files in their npm packages where both the TypeScript compiler and editors
can find them. Angular is one such library.
The `node_modules/@angular/core/` folder of any Angular application contains several `d.ts` files that describe parts of Angular.
**You need do nothing to get *typings* files for library packages that include `d.ts` files.
Angular packages include them already.**
很多库在自己的npm包中都包含了它们的类型定义文件TypeScript编译器和编辑器都能找到它们。Angular库也是这样的。
任何Angular应用程序的`node_modules/@angular/core/`目录下,都包含几个`d.ts`文件它们描述了Angular的各个部分。
**You need do nothing to get *typings* files for library packages that include `d.ts` files. Angular packages include them already.**
**我们不需要为那些包含了`d.ts`文件的库获取*类型定义*文件 —— Angular的所有包都是如此。**
### lib.d.ts
TypeScript includes a special declaration file called `lib.d.ts`. This file contains the ambient declarations for various common JavaScript constructs present in JavaScript runtimes and the DOM.
TypeScript带有一个特殊的声明文件名为`lib.d.ts`。该文件包含了JavaScript运行库和DOM的各种常用JavaScript环境声明。
Based on the `--target`, TypeScript adds _additional_ ambient declarations
like `Promise` if the target is `es6`.
基于`--target`TypeScript添加*额外*的环境声明,例如如果目标为`es6`时将添加`Promise`。
Since the QuickStart is targeting `es5`, you can override the
list of declaration files to be included:
因为《快速起步》的目标为`es5`,所以我们可以重写声明文件列表来包含:
<code-example format=".">
"lib": ["es2015", "dom"]
@ -120,23 +192,44 @@ list of declaration files to be included:
Thanks to that, you have all the `es6` typings even when targeting `es5`.
得益于这项设置,即使编译目标设置为`es5`,我们也能获得所有的`es6`类型信息。
### Installable typings files
### 安装类型定义文件
Many libraries&mdash;jQuery, Jasmine, and Lodash among them&mdash;do *not* include `d.ts` files in their npm packages.
Fortunately, either their authors or community contributors have created separate `d.ts` files for these libraries and
published them in well-known locations.
遗憾的是,很多库 —— jQuery、Jasmine和Lodash等库 —— 都*没有*在它们自己的npm包中包含`d.ts`文件。
幸运的是,它们的作者或社区中的贡献者已经为这些库创建了独立的`d.ts`文件,并且把它们发布到了一个众所周知的位置。
You can install these typings via `npm` using the
[`@types/*` scoped package](http://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html)
and Typescript, starting at 2.0, automatically recognizes them.
我们还可以通过`npm`来使用[`@types/*`范围化包](http://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html)来安装这些类型信息,
而TypeScript自从2.0开始,可以自动识别它们。
For instance, to install typings for `jasmine` you could do `npm install @types/jasmine --save-dev`.
比如,要安装`jasmine`的类型信息,我们可以执行`npm install @types/jasmine --save-dev`。
QuickStart identifies two *typings*, or `d.ts`, files:
我们在“快速起步”中指定过两个*类型定义*文件(`d.ts`
* [jasmine](http://jasmine.github.io/) typings for the Jasmine test framework.
* [node](https://www.npmjs.com/package/@types/node) for code that references objects in the *nodejs* environment;
[jasmine](http://jasmine.github.io/)是Jasmine测试框架的类型定义
* [node](https://www.npmjs.com/package/@types/node) for code that references objects in the *nodejs* environment;
you can view an example in the [webpack](guide/webpack) page.
QuickStart doesn't require these typings but many of the samples do.
[node](https://www.npmjs.com/package/@types/node)是为了在*nodejs*环境中引用对象的代码提供的类型定义。在[webpack](guide/webpack)页面可以看到例子。
QuickStart doesn't require these typings but many of the samples do.
“快速起步”本身不需要这些类型定义,但是文档中的很多例子都需要。

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
@title
User Input
用户输入
@intro
User input triggers DOM events. We listen to those events with event bindings that funnel updated values back into our components and models.
用户输入触发 DOM 事件。我们通过事件绑定来监听它们,把更新过的数据导入回我们的组件和 model。
@description
@ -12,21 +12,36 @@ text raise DOM events.
This page explains how to bind those events to component event handlers using the Angular
event binding syntax.
当用户点击链接、按下按钮或者输入文字时,这些用户动作都会产生 DOM 事件。
本章解释如何使用 Angular 事件绑定语法把这些事件绑定到事件处理器。
Run the <live-example></live-example>.
运行<live-example></live-example>
## Binding to user input events
## 绑定到用户输入事件
You can use [Angular event bindings](guide/template-syntax#event-binding)
to respond to any [DOM event](https://developer.mozilla.org/en-US/docs/Web/Events).
Many DOM events are triggered by user input. Binding to these events provides a way to
get input from the user.
你可以使用 [Angular 事件绑定](guide/template-syntax#event-binding)机制来响应任何 [DOM 事件](https://developer.mozilla.org/en-US/docs/Web/Events)。
许多 DOM 事件是由用户输入触发的。绑定这些事件可以获取用户输入。
To bind to a DOM event, surround the DOM event name in parentheses and assign a quoted
[template statement](guide/template-syntax#template-statements) to it.
要绑定 DOM 事件,只要把 DOM 事件的名字包裹在圆括号中,然后用放在引号中的[模板语句](guide/template-syntax#template-statements)对它赋值就可以了。
The following example shows an event binding that implements a click handler:
下例展示了一个事件绑定,它实现了一个点击事件处理器:
<code-example path="user-input/src/app/click-me.component.ts" region="click-me-button" title="src/app/click-me.component.ts" linenums="false">
</code-example>
@ -35,14 +50,22 @@ The following example shows an event binding that implements a click handler:
The `(click)` to the left of the equals sign identifies the button's click event as the **target of the binding**.
The text in quotes to the right of the equals sign
is the **template statement**, which responds
is the **template statement**, which reponds
to the click event by calling the component's `onClickMe` method.
等号左边的`(click)`表示把按钮的点击事件作为**绑定目标**。
等号右边引号中的文本是**模板语句**,通过调用组件的`onClickMe`方法来响应这个点击事件。
When writing a binding, be aware of a template statement's **execution context**.
The identifiers in a template statement belong to a specific context object,
usually the Angular component controlling the template.
The example above shows a single line of HTML, but that HTML belongs to a larger component:
写绑定时,需要知道模板语句的**执行上下文**。
出现在模板语句中的每个标识符都属于特定的上下文对象。
这个对象通常都是控制此模板的 Angular 组件。
上例中只显示了一行 HTML那段 HTML 片段属于下面这个组件:
<code-example path="user-input/src/app/click-me.component.ts" region="click-me-component" title="src/app/click-me.component.ts" linenums="false">
@ -52,14 +75,23 @@ The example above shows a single line of HTML, but that HTML belongs to a larger
When the user clicks the button, Angular calls the `onClickMe` method from `ClickMeComponent`.
当用户点击按钮时Angular 调用`ClickMeComponent`的`onClickMe`方法。
## Get user input from the $event object
## 通过 $event 对象取得用户输入
DOM events carry a payload of information that may be useful to the component.
This section shows how to bind to the `keyup` event of an input box to get the user's input after each keystroke.
DOM 事件可以携带可能对组件有用的信息。
本节将展示如何绑定输入框的`keyup`事件,在每个敲击键盘时获取用户输入。
The following code listens to the `keyup` event and passes the entire event payload (`$event`) to the component event handler.
下面的代码监听`keyup`事件,并将整个事件载荷 (`$event`) 传递给组件的事件处理器。
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-1-template" title="src/app/keyup.components.ts (template v.1)" linenums="false">
</code-example>
@ -69,7 +101,11 @@ The following code listens to the `keyup` event and passes the entire event payl
When a user presses and releases a key, the `keyup` event occurs, and Angular provides a corresponding
DOM event object in the `$event` variable which this code passes as a parameter to the component's `onKey()` method.
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-1-class-no-type" title="src/app/keyup.components.ts (class v.1)" linenums="false">
当用户按下并释放一个按键时,触发`keyup`事件Angular 在`$event`变量提供一个相应的 DOM
事件对象,上面的代码将它作为参数传递给`onKey()`方法。
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-1-class-no-type" title="src/app/keyup.components.ts (类 v.1)" linenums="false">
</code-example>
@ -78,19 +114,32 @@ DOM event object in the `$event` variable which this code passes as a parameter
The properties of an `$event` object vary depending on the type of DOM event. For example,
a mouse event includes different information than a input box editing event.
`$event`对象的属性取决于 DOM 事件的类型。例如,鼠标事件与输入框编辑事件包含了不同的信息。
All [standard DOM event objects](https://developer.mozilla.org/en-US/docs/Web/API/Event)
have a `target` property, a reference to the element that raised the event.
In this case, `target` refers to the [`<input>` element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) and
`event.target.value` returns the current contents of that element.
所有[标准 DOM 事件对象](https://developer.mozilla.org/en-US/docs/Web/API/Event)都有一个`target`属性,
引用触发该事件的元素。
在本例中,`target`是[`<input>`元素](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement)
`event.target.value`返回该元素的当前内容。
After each call, the `onKey()` method appends the contents of the input box value to the list
in the component's `values` property, followed by a separator character (|).
The [interpolation](guide/template-syntax#interpolation)
displays the accumulating input box changes from the `values` property.
displays the accumulating input box changes from the `values` property.
在组件的`onKey()`方法中,把输入框的值和分隔符 (|) 追加组件的`values`属性。
使用[插值表达式](guide/template-syntax#interpolation)来把存放累加结果的`values`属性回显到屏幕上。
Suppose the user enters the letters "abc", and then backspaces to remove them one by one.
Here's what the UI displays:
假设用户输入字母“abc”然后用退格键一个一个删除它们。
用户界面将显示:
<code-example>
a | ab | abc | ab | a | |
</code-example>
@ -110,6 +159,8 @@ Here's what the UI displays:
Alternatively, you could accumulate the individual keys themselves by substituting `event.key`
for `event.target.value` in which case the same user input would produce:
或者,你可以用`event.key`替代`event.target.value`,积累各个按键本身,这样同样的用户输入可以产生:
<code-example>
a | b | c | backspace | backspace | backspace |
@ -126,43 +177,73 @@ for `event.target.value` in which case the same user input would produce:
### Type the _$event_
### *$event*的类型
The example above casts the `$event` as an `any` type.
That simplifies the code at a cost.
That simplifies the code at a cost.
There is no type information
that could reveal properties of the event object and prevent silly mistakes.
上例将`$event`转换为`any`类型。
这样简化了代码,但是有成本。
没有任何类型信息能够揭示事件对象的属性,防止简单的错误。
The following example rewrites the method with types:
下面的例子,使用了带类型方法:
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-1-class" title="src/app/keyup.components.ts (class v.1 - typed )" linenums="false">
</code-example>
The `$event` is now a specific `KeyboardEvent`.
The `$event` is now a specific `KeyboardEvent`.
Not all elements have a `value` property so it casts `target` to an input element.
The `OnKey` method more clearly expresses what it expects from the template and how it interprets the event.
`$event`的类型现在是`KeyboardEvent`。
不是所有的元素都有`value`属性,所以它将`target`转换为输入元素。
`OnKey`方法更加清晰的表达了它期望从模板得到什么,以及它是如何解析事件的。
### Passing _$event_ is a dubious practice
### 传入 *$event* 是靠不住的做法
Typing the event object reveals a significant objection to passing the entire DOM event into the method:
the component has too much awareness of the template details.
It can't extract information without knowing more than it should about the HTML implementation.
That breaks the separation of concerns between the template (_what the user sees_)
That breaks the separation of concerns between the template (_what the user sees_)
and the component (_how the application processes user data_).
类型化事件对象揭露了重要的一点,即反对把整个 DOM 事件传到方法中,因为这样组件会知道太多模板的信息。
只有当它知道更多它本不应了解的 HTML 实现细节时,它才能提取信息。
这就违反了模板(*用户看到的*)和组件(*应用如何处理用户数据*)之间的分离关注原则。
The next section shows how to use template reference variables to address this problem.
下面将介绍如何用模板引用变量来解决这个问题。
## Get user input from a template reference variable
## 从一个模板引用变量中获得用户输入
There's another way to get the user data: use Angular
[**template reference variables**](guide/template-syntax#ref-vars).
These variables provide direct access to an element from within the template.
To declare a template reference variable, precede an identifier with a hash (or pound) character (#).
还有另一种获取用户数据的方式:使用 Angular 的[**模板引用变量**](guide/template-syntax#ref-vars)。
这些变量提供了从模块中直接访问元素的能力。
在标识符前加上井号 (#) 就能声明一个模板引用变量。
The following example uses a template reference variable
to implement a keystroke loopback in a simple template.
下面的例子使用了局部模板变量,在一个超简单的模板中实现按键反馈功能。
<code-example path="user-input/src/app/loop-back.component.ts" region="loop-back-component" title="src/app/loop-back.component.ts" linenums="false">
</code-example>
@ -174,14 +255,21 @@ refers to the `<input>` element itself.
The code uses the `box` variable to get the input element's `value` and display it
with interpolation between `<p>` tags.
这个模板引用变量名叫`box`,在`<input>`元素声明,它引用`<input>`元素本身。
代码使用`box`获得输入元素的`value`值,并通过插值表达式把它显示在`<p>`标签中。
The template is completely self contained. It doesn't bind to the component,
and the component does nothing.
这个模板完全是完全自包含的。它没有绑定到组件,组件也没做任何事情。
Type something in the input box, and watch the display update with each keystroke.
在输入框中输入,就会看到每次按键时,显示也随之更新了。
<figure class='image-display'>
<img src='assets/images/devguide/user-input/keyup-loop-back-anim.gif' alt="loop back"></img>
<img src='assets/images/devguide/user-input/keyup-loop-back-anim.gif' alt="反馈"></img>
</figure>
@ -192,13 +280,21 @@ Type something in the input box, and watch the display update with each keystrok
**This won't work at all unless you bind to an event**.
**除非你绑定一个事件,否则这将完全无法工作。**
Angular updates the bindings (and therefore the screen)
only if the app does something in response to asynchronous events, such as keystrokes.
只有在应用做了些异步事件如击键Angular 才更新绑定(并最终影响到屏幕)。
This example code binds the `keyup` event
to the number 0, the shortest template statement possible.
While the statement does nothing useful,
to the number 0, the shortest template statement possible.
While the statement does nothing useful,
it satisfies Angular's requirement so that Angular will update the screen.
本例代码将`keyup`事件绑定到了数字0这是可能是最短的模板语句。
虽然这个语句不做什么,但它满足 Angular 的要求,所以 Angular 将更新屏幕。
</div>
@ -207,6 +303,9 @@ It's easier to get to the input box with the template reference
variable than to go through the `$event` object. Here's a rewrite of the previous
`keyup` example that uses a template reference variable to get the user's input.
从模板变量获得输入框比通过`$event`对象更加简单。
下面的代码重写了之前`keyup`示例,它使用变量来获得用户输入。
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-2" title="src/app/keyup.components.ts (v2)" linenums="false">
</code-example>
@ -215,17 +314,29 @@ variable than to go through the `$event` object. Here's a rewrite of the previou
A nice aspect of this approach is that the component gets clean data values from the view.
It no longer requires knowledge of the `$event` and its structure.
这个方法最漂亮的一点是:组件代码从视图中获得了干净的数据值。再也不用了解`$event`变量及其结构了。
{@a key-event}
## Key event filtering (with `key.enter`)
## 按键事件过滤(通过`key.enter`
The `(keyup)` event handler hears *every keystroke*.
Sometimes only the _Enter_ key matters, because it signals that the user has finished typing.
One way to reduce the noise would be to examine every `$event.keyCode` and take action only when the key is _Enter_.
There's an easier way: bind to Angular's `keyup.enter` pseudo-event.
`(keyup)`事件处理器监听*每一次按键*。
有时只在意*回车*键,因为它标志着用户结束输入。
解决这个问题的一种方法是检查每个`$event.keyCode`,只有键值是*回车*键时才采取行动。
There's an easier way: bind to Angular's `keyup.enter` pseudo-event.
Then Angular calls the event handler only when the user presses _Enter_.
更简单的方法是:绑定到 Angular 的`keyup.enter` 模拟事件。
然后,只有当用户敲*回车*键时Angular 才会调用事件处理器。
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-3" title="src/app/keyup.components.ts (v3)" linenums="false">
</code-example>
@ -234,6 +345,8 @@ Then Angular calls the event handler only when the user presses _Enter_.
Here's how it works.
下面展示了它是如何工作的。
<figure class='image-display'>
<img src='assets/images/devguide/user-input/keyup3-anim.gif' alt="key up 3"></img>
</figure>
@ -243,13 +356,20 @@ Here's how it works.
## On blur
## 失去焦点事件 (blur)
In the previous example, the current state of the input box
is lost if the user mouses away and clicks elsewhere on the page
without first pressing _Enter_.
The component's `value` property is updated only when the user presses _Enter_.
前上例中,如果用户没有先按回车键,而是移开了鼠标,点击了页面中其它地方,输入框的当前值就会丢失。
只有当用户按下了回车键候,组件的`values`属性才能更新。
To fix this issue, listen to both the _Enter_ key and the _blur_ event.
下面通过同时监听输入框的回车键和失去焦点事件来修正这个问题。
<code-example path="user-input/src/app/keyup.components.ts" region="key-up-component-4" title="src/app/keyup.components.ts (v4)" linenums="false">
@ -259,23 +379,33 @@ To fix this issue, listen to both the _Enter_ key and the _blur_ event.
## Put it all together
## 把它们放在一起
The previous page showed how to [display data](guide/displaying-data).
This page demonstrated event binding techniques.
上一章介绍了如何[显示数据](guide/displaying-data)。
本章展示了事件绑定技术。
Now, put it all together in a micro-app
that can display a list of heroes and add new heroes to the list.
The user can add a hero by typing the hero's name in the input box and
clicking **Add**.
现在,在一个微型应用中一起使用它们,应用能显示一个英雄列表,并把新的英雄加到列表中。
用户可以通过输入英雄名和点击“添加”按钮来添加英雄。
<figure class='image-display'>
<img src='assets/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes"></img>
<img src='assets/images/devguide/user-input/little-tour-anim.gif' alt="简版英雄指南"></img>
</figure>
Below is the "Little Tour of Heroes" component.
下面就是“简版英雄指南”组件。
<code-example path="user-input/src/app/little-tour.component.ts" region="little-tour" title="src/app/little-tour.component.ts" linenums="false">
@ -284,26 +414,41 @@ Below is the "Little Tour of Heroes" component.
### Observations
### 小结
* **Use template variables to refer to elements** &mdash;
The `newHero` template variable refers to the `<input>` element.
You can reference `newHero` from any sibling or child of the `<input>` element.
**使用模板变量来引用元素** &mdash; `newHero`模板变量引用了`<input>`元素。
你可以在`<input>`的任何兄弟或子级元素中引用`newHero`。
* **Pass values, not elements** &mdash;
Instead of passing the `newHero` into the component's `addHero` method,
get the input box value and pass *that* to `addHero`.
**传递数值,而非元素** &mdash;
获取输入框的值并将*它*传递给组件的`addHero`,而不要传递`newHero`。
* **Keep template statements simple** &mdash;
The `(blur)` event is bound to two JavaScript statements.
The first statement calls `addHero`. The second statement, `newHero.value=''`,
clears the input box after a new hero is added to the list.
**保持模板语句简单** &mdash;
`(blur)`事件被绑定到两个 JavaScript 语句。
第一句调用`addHero`。第二句`newHero.value=''`在添加新英雄到列表中后清除输入框。
## Source code
## 源代码
Following is all the code discussed in this page.
下面是本章讨论过的所有源码。
<code-tabs>
<code-pane title="click-me.component.ts" path="user-input/src/app/click-me.component.ts">
@ -329,11 +474,19 @@ Following is all the code discussed in this page.
## Summary
## 小结
You have mastered the basic primitives for responding to user input and gestures.
你已经掌握了响应用户输入和操作的基础技术。
These techniques are useful for small-scale demonstrations, but they
quickly become verbose and clumsy when handling large amounts of user input.
Two-way data binding is a more elegant and compact way to move
values between data entry fields and model properties.
The next page, `Forms`, explains how to write
two-way bindings with `NgModel`.
two-way bindings with `NgModel`.
这些技术对小规模演示很实用,但是在处理大量用户输入时,很容易变得累赘和笨拙。
要在数据录入字段和模型属性之间传递数据,双向数据绑定是更加优雅和简洁的方式。
下一章`表单`解释了如何用`NgModel`来进行双向绑定。

View File

@ -1,17 +1,22 @@
@title
Visual Studio 2015 QuickStart
Visual Studio 2015 快速起步
@intro
Use Visual Studio 2015 with the QuickStart files.
使用 Visual Studio 2015 快速起步
@description
{@a top}
Some developers prefer Visual Studio as their Integrated Development Environment (IDE).
有些开发者喜欢用Visual Studio。
This cookbook describes the steps required to set up and use the
Angular QuickStart files in **Visual Studio 2015 within an ASP.NET 4.x project**.
本烹饪宝典介绍了在**Visual Studio 2015的ASP.NET 4.x项目中**用Angular实现“快速起步”所需的步骤。
<div class="l-sub-section">
@ -19,6 +24,8 @@ Angular QuickStart files in **Visual Studio 2015 within an ASP.NET 4.x project**
There is no *live example* for this cookbook because it describes Visual Studio, not
the QuickStart application itself.
本烹饪宝典中没有*在线例子*因为它介绍的是Visual Studio而不是《快速起步》应用程序本身。
</div>
@ -27,20 +34,50 @@ the QuickStart application itself.
## ASP.NET 4.x Project
## ASP.NET 4.x 项目
To set up the QuickStart files with an **ASP.NET 4.x project** in
Visual Studio 2015, follow these steps:
要用Visual Studio 2015在**ASP.NET 4.x项目**中设置**《快速起步》**文件,请遵循如下步骤:
* [Prerequisite](guide/visual-studio-2015#prereq1): Install Node.js.
[前提条件](guide/visual-studio-2015#prereq1): 安装Node.js
* [Prerequisite](guide/visual-studio-2015#prereq2): Install Visual Studio 2015 Update 3.
[前提条件](guide/visual-studio-2015#prereq2): 安装Visual Studio 2015 Update 3
* [Prerequisite](guide/visual-studio-2015#prereq3): Configure external web tools.
[前提条件](guide/visual-studio-2015#prereq3): 配置External Web tools
* [Prerequisite](guide/visual-studio-2015#prereq4): Install TypeScript 2.2 for Visual Studio 2015.
[前提条件](guide/visual-studio-2015#prereq4): 安装TypeScript 2 for Visual Studio 2015
* [Step 1](guide/visual-studio-2015#download): Download the QuickStart files.
[第一步](guide/visual-studio-2015#download): 下载“快速起步”的文件
* [Step 2](guide/visual-studio-2015#create-project): Create the Visual Studio ASP.NET project.
[第二步](guide/visual-studio-2015#create-project): 创建Visual Studio ASP.NET项目
* [Step 3](guide/visual-studio-2015#copy): Copy the QuickStart files into the ASP.NET project folder.
[第三步](guide/visual-studio-2015#copy): 把“快速起步”中的文件拷贝到ASP.NET的项目目录中
* [Step 4](guide/visual-studio-2015#restore): Restore required packages.
[第四步](guide/visual-studio-2015#restore): 恢复需要的包
* [Step 5](guide/visual-studio-2015#build-and-run): Build and run the app.
[第五步](guide/visual-studio-2015#build-and-run): 构建和运行应用程序
<div class="l-sub-section">
@ -51,6 +88,10 @@ then consider the _experimental_
<a href="http://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/" target="_blank">ASP.NET Core + Angular template for Visual Studio 2015</a>.
Note that the resulting code does not map to the docs. Adjust accordingly.
如果你希望使用**ASP.NET Core**并体验全新项目,
参见_预览版_<a href="http://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/" target="_blank">ASP.NET Core + Angular template for Visual Studio 2015</a>
注意,最终代码与本文不对应,请适当调节。
</div>
@ -58,7 +99,15 @@ Note that the resulting code does not map to the docs. Adjust accordingly.
<h2 id='prereq1'>
Prerequisite: Node.js
<p>
Prerequisite: Node.js
</p>
<p>
前提条件: Node.js
</p>
</h2>
@ -66,6 +115,9 @@ Note that the resulting code does not map to the docs. Adjust accordingly.
Install **[Node.js® and npm](https://nodejs.org/en/download/)**
if they are not already on your machine.
如果你的电脑里没有Node.js®和npm请安装**[它们](https://nodejs.org/en/download/)**。
<div class="l-sub-section">
@ -74,6 +126,7 @@ if they are not already on your machine.
by running `node -v` and `npm -v` in a terminal window.
Older versions produce errors.
在终端或者控制台中运行`node -v`和`npm -v`**请确认你的Node版本为`4.6.x`或更高npm的版本为`3.x.x`或更高**。老版本会引起错误。
</div>
@ -81,7 +134,15 @@ Older versions produce errors.
<h2 id='prereq2'>
Prerequisite: Visual Studio 2015 Update 3
<p>
Prerequisite: Visual Studio 2015 Update 3
</p>
<p>
前提条件: Visual Studio 2015 Update 3
</p>
</h2>
@ -90,34 +151,78 @@ The minimum requirement for developing Angular applications with Visual Studio i
Earlier versions do not follow the best practices for developing applications with TypeScript.
To view your version of Visual Studio 2015, go to `Help | About Visual Studio`.
使用Visual Studio开发Angular应用程序的最低要求是Update 3。
早期版本没有遵循使用TypeScript开发应用程序的最佳实践。
要查看你的Visual Studio 2015版本号到`Help | About Visual Studio`。
If you don't have it, install **[Visual Studio 2015 Update 3](https://www.visualstudio.com/en-us/news/releasenotes/vs2015-update3-vs)**.
Or use `Tools | Extensions and Updates` to update to Update 3 directly from Visual Studio 2015.
如果还没有,安装**[Visual Studio 2015 Update 3](https://www.visualstudio.com/en-us/news/releasenotes/vs2015-update3-vs)**。或者使用`Tools | Extensions and Updates`来直接在Visual Studio 2015中更新到Update 3。
<h2 id='prereq3'>
Prerequisite: Configure External Web tools
<p>
Prerequisite: Configure External Web tools
</p>
<p>
前提条件: 配置External Web tools
</p>
</h2>
Configure Visual Studio to use the global external web tools instead of the tools that ship with Visual Studio:
配置Visual Studio来使用全局External Web Tools而非Visual Studio默认的工具
* Open the **Options** dialog with `Tools` | `Options`.
到`Tools` | `Options`打开**Options**对话框
* In the tree on the left, select `Projects and Solutions` | `External Web Tools`.
在左边树型项目中,选择`Projects and Solutions` | `External Web Tools`
* On the right, move the `$(PATH)` entry above the `$(DevEnvDir`) entries. This tells Visual Studio to
use the external tools (such as npm) found in the global path before using its own version of the external tools.
* Click OK to close the dialog.
* Restart Visual Studio for this change to take effect.
* 在右侧,将`$(PATH)`移动到 `$(DevEnvDir`)上面。这样Visual Stuio就会在使用自带的外部工具时优先使用全局路径中的外部工具比如npm
Click OK to close the dialog.
* 点击OK关闭对话框。
Restart Visual Studio for this change to take effect.
* 重启Visual Studio以让设置变化生效。
Visual Studio now looks first for external tools in the current workspace and
if it doesn't find them, it looks in the global path. If Visual Studio doesn't
find them in either location, it will use its own versions of the tools.
Visual Studio将优先在当前的工作区查找外部工具如果没有找到便查找全局路径如果还没有找到Visual Studio就使用自带的工具版本。
<h2 id='prereq4'>
Prerequisite: Install TypeScript 2.2 for Visual Studio 2015
<p>
Prerequisite: Install TypeScript 2.2 for Visual Studio 2015
</p>
<p>
前提条件: 安装TypeScript 2.2 for Visual Studio 2015
</p>
</h2>
@ -125,21 +230,41 @@ find them in either location, it will use its own versions of the tools.
While Visual Studio Update 3 ships with TypeScript support out of the box, it currently doesnt ship with TypeScript 2.2,
which you need to develop Angular applications.
Visual Studio Update 3自带TypeScript支持但是它的TypeScript版本开发Angular应用需要的不是2.2。
To install TypeScript 2.2:
要安装TypeScript 2.2
* Download and install **[TypeScript 2.2 for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48593)**
下载并安装 **[TypeScript 2.2 for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48593)**
* OR install it with npm: `npm install -g typescript@2.2`.
或通过npm安装`npm install -g typescript@2.2`。
You can find out more about TypeScript 2.2 support in Visual studio **[here](https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/)**.
你可以在**[这儿](https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/)**查看更多Visual Studio中TypeScript 2.2的支持。
At this point, Visual Studio is ready. Its a good idea to close Visual Studio and
restart it to make sure everything is clean.
至此Visual Studio准备好了。重新启动Visual Stuido这样我们可以有一个崭新的开始。
<h2 id='download'>
Step 1: Download the QuickStart files
<p>
Step 1: Download the QuickStart files
</p>
<p>
第一步: 现在“快速起步”文件
</p>
</h2>
@ -147,21 +272,48 @@ restart it to make sure everything is clean.
[Download the QuickStart source](https://github.com/angular/quickstart)
from GitHub. If you downloaded as a zip file, extract the files.
从GitHub[下载“快速起步”的源代码](https://github.com/angular/quickstart)。如果下载的是一个压缩的zip文件解压它。
<h2 id='create-project'>
Step 2: Create the Visual Studio ASP.NET project
<p>
Step 2: Create the Visual Studio ASP.NET project
</p>
<p>
第二步创建Visual Studio ASP.net项目
</p>
</h2>
Create the ASP.NET 4.x project in the usual way as follows:
按照下列步骤创建ASP.NET 4.x项目
* In Visual Studio, select `File` | `New` | `Project` from the menu.
在Visual Studio中选择`File` | `New` | `Project`菜单。
* In the template tree, select `Templates` | `Visual C#` (or `Visual Basic`) | `Web`.
在模板树中,选择`Templates` | `Visual C#`(或`Visual Basic`) | `Web`菜单。
* Select the `ASP.NET Web Application` template, give the project a name, and click OK.
选择`ASP.NET Web Application`模板输入项目名点击“OK”按钮。
* Select the desired ASP.NET 4.5.2 template and click OK.
选择自己喜欢的ASP.NET 4.5.2模板点击OK。
<div class="l-sub-section">
@ -170,6 +322,8 @@ Create the ASP.NET 4.x project in the usual way as follows:
This cookbook uses the `Empty` template with no added folders,
no authentication, and no hosting. Pick the template and options appropriate for your project.
本烹饪宝典选择了`Empty`模板,它没有添加过任何目录,没有身份验证,没有服务器托管。为你的项目选择合适的模板和选项。
</div>
@ -182,39 +336,96 @@ no authentication, and no hosting. Pick the template and options appropriate for
<h2 id='copy'>
第三步: 把“快速起步”的文件复制到ASP.NET项目所在的目录
</h2>
Copy the QuickStart files you downloaded from GitHub into the folder containing the `.csproj` file.
Include the files in the Visual Studio project as follows:
拷贝从GitHub下载的“快速起步”文件到包含`.csproj`文件的目录中。按照下面的步骤把它们加到Visual Studio中
* Click the `Show All Files` button in Solution Explorer to reveal all of the hidden files in the project.
在Solution Explorer中点击`Show All Files`按钮,显示项目中所有隐藏文件。
* Right-click on each folder/file to be included in the project and select `Include in Project`.
Minimally, include the following folder/files:
右键点击每个目录和文件,选择`Include in Project`。
最少要添加下列文件:
* src/app folder (answer *No* if asked to search for TypeScript Typings)
src/app目录如果询问是否要搜索TypeScript类型回答*No*
* src/styles.css
* src/index.html
* package.json
* src/tsconfig.json
* typings.json
<h2 id='restore'>
Step 4: Restore the required packages
<p>
Step 4: Restore the required packages
</p>
<p>
第四步: 恢复需要的包
</p>
</h2>
Restore the packages required for an Angular application as follows:
按下面的步骤恢复Angular应用程序需要的包
* Right-click on the `package.json` file in Solution Explorer and select `Restore Packages`.
<br>This uses `npm` to install all of the packages defined in the `package.json` file.
It may take some time.
在Solution Explorer中右键点击`package.json`,选择`Restore Packages`。
<br>这样Visual Studio会使用`npm`来安装在`package.json`中定义的所有包.
这可能需要花一点时间。
* If desired, open the Output window (`View` | `Output`) to watch the npm commands execute.
如果愿意打开Output窗口(`View` | `Output`)来监控npm命令的执行情况。
* Ignore the warnings.
忽略所有警告。
* When the restore is finished, a message in the bottom message bar of Visual Studio
should say: `Installing packages complete`. Be patient. This could take a while.
当恢复完成后,将会出现一条消息:`Installing packages complete`。耐心点,这相当耗时间。
* Click the `Refresh` icon in Solution Explorer.
在Solution Explorer里点击`Refresh`图标。
* **Do not** include the `node_modules` folder in the project. Let it be a hidden project folder.
**不要**将`node_modules`目录添加到项目中,让它隐藏。
<h2 id='build-and-run'>
@ -223,9 +434,18 @@ Restore the packages required for an Angular application as follows:
<h2 id='build-and-run'>
第五步:构建和运行应用
</h2>
First, ensure that `src/index.html` is set as the start page.
Right-click `index.html` in Solution Explorer and select option `Set As Start Page`.
首先,确认`src/index.html`已被设置为开始页面。
在Solution Explorer中右键点击`index.html`,选择选项`Set As Start Page`。
### To run in VS with F5
Most Visual Studio developers like to press the F5 key and see the IIS server come up.
@ -315,6 +535,8 @@ match the base href in `index.html`.
Build and launch the app with debugger by clicking the **Run** button or by pressing `F5`.
点击**Run**按钮或者按`F5`键,用调试器构建和启动应用。
<div class="l-sub-section">
@ -322,11 +544,18 @@ Build and launch the app with debugger by clicking the **Run** button or by pres
It's faster to run without the debugger by pressing `Ctrl-F5`.
按`Ctrl-F5`不带调试器的运行应用,速度会更快。
</div>
The default browser opens and displays the QuickStart sample application.
默认浏览器打开并显示《快速起步》例子应用。
Try editing any of the project files. Save and refresh the browser to
see the changes.
see the changes.
尝试编辑任何项目文件,*保存*并刷新浏览器来查看效果。

View File

@ -1,8 +1,8 @@
@title
Webpack: an introduction
Webpack 简介
@intro
Create Angular applications with a Webpack based tooling.
使用基于 Webpack 的工具创建 Angular 应用
@description
@ -19,61 +19,133 @@ Create Angular applications with a Webpack based tooling.
a tool for bundling application source code in convenient _chunks_
and for loading that code from a server into a browser.
[**Webpack**](https://webpack.github.io/)是一个广受欢迎的模块打包器,
这个工具用来把程序源码打包到一些方便易用的_块_中以便把这些代码从服务器加载到浏览器中。
It's an excellent alternative to the *SystemJS* approach used elsewhere in the documentation.
This guide offers a taste of Webpack and explains how to use it with Angular applications.
它是我们在文档中到处使用的*SystemJS*的一个优秀替代品。这篇指南会带我们尝尝Webpack的滋味并解释如何在Angular程序中使用它。
{@a top}
# Contents
# 目录
* [What is Webpack?](guide/webpack#what-is-webpack)
[什么是Webpack](guide/webpack#what-is-webpack)
* [Entries and outputs](guide/webpack#entries-outputs)
[入口与输出](guide/webpack#entries-outputs)
* [Multiple bundles](guide/webpack#multiple-bundles)
[多重包](guide/webpack#multiple-bundles)
* [Loaders](guide/webpack#loaders)
[加载器](guide/webpack#loaders)
* [Plugins](guide/webpack#plugins)
[插件](guide/webpack#plugins)
* [Configuring Webpack](guide/webpack#configure-webpack)
[配置Webpack](guide/webpack#configure-webpack)
* [Polyfills](guide/webpack#polyfills)
* [Common configuration](guide/webpack#common-configuration)
[公共配置](guide/webpack#common-configuration)
* [Inside `webpack.common.js`](guide/webpack#inside-webpack-commonjs)
[深入`webpack.common.js`](guide/webpack#inside-webpack-commonjs)
* [entry](guide/webpack#common-entries)
[入口](guide/webpack#common-entries)
* [resolve extension-less imports](guide/webpack#common-resolves)
[解析无扩展名的导入](guide/webpack#common-resolves)
* [`module.rules`](guide/webpack#common-rules)
* [Plugins](guide/webpack#plugins)
[插件](guide/webpack#plugins)
* [`CommonsChunkPlugin`](guide/webpack#commons-chunk-plugin)
* [`HtmlWebpackPlugin`](guide/webpack#html-webpack-plugin)
* [Environment specific configuration](guide/webpack#environment-configuration)
[针对特定环境进行配置](guide/webpack#environment-configuration)
* [Development configuration](guide/webpack#development-configuration)
[开发环境配置](guide/webpack#development-configuration)
* [Production configuration](guide/webpack#production-configuration)
[生产环境配置](guide/webpack#production-configuration)
* [Test configuration](guide/webpack#test-configuration)
[测试环境配置](guide/webpack#test-configuration)
* [Trying it out](guide/webpack#try)
[试一下](guide/webpack#try)
* [Highlights](guide/webpack#highlights)
[重点](guide/webpack#highlights)
* [Conclusion](guide/webpack#conclusion)
[总结](guide/webpack#conclusion)
You can also <a href="/resources/zips/webpack/webpack.zip">download the final result.</a>
你还可以<a href="/resources/zips/webpack/webpack.zip">点这里下载最终结果</a>
{@a what-is-webpack}
## What is Webpack?
Webpack is a powerful module bundler.
A _bundle_ is a JavaScript file that incorporates _assets_ that *belong* together and
## 什么是Webpack
Webpack is a powerful module bundler.
A _bundle_ is a JavaScript file that incorporates _assets_ that *belong* together and
should be served to the client in a response to a single file request.
A bundle can include JavaScript, CSS styles, HTML, and almost any other kind of file.
Webpack是一个强力的模块打包器。
所谓_包(bundle)_就是一个JavaScript文件它把一堆_资源(assets)_合并在一起以便它们可以在同一个文件请求中发回给客户端。
包中可以包含JavaScript、CSS样式、HTML以及很多其它类型的文件。
Webpack roams over your application source code,
looking for `import` statements, building a dependency graph, and emitting one or more _bundles_.
With plugins and rules, Webpack can preprocess and minify different non-JavaScript files such as TypeScript, SASS, and LESS files.
Webpack会遍历你应用中的所有源码查找`import`语句,构建出依赖图谱,并产出一个(或多个)_包_。
通过插件和规则Webpack可以对各种非JavaScript文件进行预处理和最小化(Minify)比如TypeScript、SASS和LESS文件等。
You determine what Webpack does and how it does it with a JavaScript configuration file, `webpack.config.js`.
我们通过一个JavaScript配置文件`webpack.config.js`来决定Webpack做什么以及如何做。
{@a entries-outputs}
@ -81,9 +153,14 @@ You determine what Webpack does and how it does it with a JavaScript configurati
### Entries and outputs
You supply Webpack with one or more *entry* files and let it find and incorporate the dependencies that radiate from those entries.
### 入口与输出
You supply Webpack with one or more *entry* files and let it find and incorporate the dependencies that radiate from those entries.
The one entry point file in this example is the application's root file, `src/main.ts`:
我们给Webpack提供一个或多个*入口*文件,来让它查找与合并那些从这些入口点发散出去的依赖。
在下面这个例子中,我们的入口点是该应用的根文件`src/app.ts`
<code-example path="webpack/config/webpack.common.js" region="one-entry" title="webpack.config.js (single entry)" linenums="false">
@ -93,6 +170,8 @@ The one entry point file in this example is the application's root file, `src/ma
Webpack inspects that file and traverses its `import` dependencies recursively.
Webpack探查那个文件并且递归遍历它的`import`依赖。
<code-example path="webpack/src/app/app.component.ts" region="component" title="src/main.ts" linenums="false">
@ -103,8 +182,13 @@ Webpack inspects that file and traverses its `import` dependencies recursively.
It sees that you're importing `@angular/core` so it adds that to its dependency list for potential inclusion in the bundle.
It opens the `@angular/core` file and follows _its_ network of `import` statements until it has built the complete dependency graph from `main.ts` down.
这里Webpack看到我们正在导入`@angular/core`,于是就这个文件加入到它的依赖列表里,为(有可能)把该文件打进包中做准备。
它打开`@angular/core`并追踪由_该文件的_`import`语句构成的网络,直到构建出从`main.ts`往下的整个依赖图谱。
Then it **outputs** these files to the `app.js` _bundle file_ designated in configuration:
然后它把这些文件**输出**到当前配置所指定的_包文件_`app.js`中:
<div class='code-example'>
@ -122,16 +206,26 @@ Then it **outputs** these files to the `app.js` _bundle file_ designated in conf
This `app.js` output bundle is a single JavaScript file that contains the application source and its dependencies.
You'll load it later with a `<script>` tag in the `index.html`.
这个`app.js`输出包是个单一的JavaScript文件它包含程序的源码及其所有依赖。
后面我们将在`index.html`中用`<script>`
{@a multiple-bundles}
#### Multiple bundles
#### 多重包
You probably don't want one giant bundle of everything.
It's preferable to separate the volatile application app code from comparatively stable vendor code modules.
我们可能不会希望把所有东西打进一个巨型包,而更喜欢把多变的应用代码从相对稳定的第三方提供商模块中分离出来。
Change the configuration so that it has two entry points, `main.ts` and `vendor.ts`:
所以要修改配置,以获得两个入口点:`main.ts`和`vendor.ts`
<div class='code-example'>
@ -155,6 +249,8 @@ Webpack constructs two separate dependency graphs
and emits *two* bundle files, one called `app.js` containing only the application code and
another called `vendor.js` with all the vendor dependencies.
Webpack会构造出两个独立的依赖图谱并产出*两个*包文件:一个叫做`app.js`,它只包含我们的应用代码;另一个叫做`vendor.js`,它包含所有的提供商依赖。
<div class="l-sub-section">
@ -163,6 +259,8 @@ another called `vendor.js` with all the vendor dependencies.
The `[name]` in the output name is a *placeholder* that a Webpack plugin replaces with the entry names,
`app` and `vendor`. Plugins are [covered later](guide/webpack#commons-chunk-plugin) in the guide.
在输出文件名中出现的`[name]`是一个Webpack的*占位符*它将被一个Webpack插件替换为入口点的名字分别是`app`和`vendor`。插件在本章的[稍后部分](guide/webpack#commons-chunk-plugin)讲解。
</div>
@ -171,6 +269,9 @@ The `[name]` in the output name is a *placeholder* that a Webpack plugin replace
To tell Webpack what belongs in the vendor bundle,
add a `vendor.ts` file that only imports the application's third-party modules:
要想告诉Webpack哪些文件属于vendor包可以添加一个`vendor.ts`文件,它只导入该应用的第三方模块:
<code-example path="webpack/src/vendor.ts" title="src/vendor.ts" linenums="false">
</code-example>
@ -183,23 +284,29 @@ add a `vendor.ts` file that only imports the application's third-party modules:
### Loaders
### 加载器(Loader)
Webpack can bundle any kind of file: JavaScript, TypeScript, CSS, SASS, LESS, images, HTML, fonts, whatever.
Webpack _itself_ only understands JavaScript files.
Teach it to transform non-JavaScript file into their JavaScript equivalents with *loaders*.
Configure loaders for TypeScript and CSS as follows.
Webpack可以打包任何类型的文件JavaScript、TypeScript、CSS、SASS、LESS、图片、HTML以及字体文件等等。
但Webpack*本身*只认识JavaScript文件。
我们要通过*加载器*来告诉它如何把这些文件处理成JavaScript文件。
在这里我们为TypeScript和CSS文件配置了加载器。
<div class='code-example'>
<code-example language="javascript">
rules: [
{
test: /\.ts$/,
test:/\.ts$/,
loader: 'awesome-typescript-loader'
},
{
test: /\.css$/,
loaders: 'style-loader!css-loader'
test: /\.css$/,loaders: 'style-loader!css-loader'
}
]
@ -212,6 +319,8 @@ Configure loaders for TypeScript and CSS as follows.
When Webpack encounters `import` statements like the following,
it applies the `test` RegEx patterns.
当Webpack遇到如下所示的`import`语句时,它就会调用正则表达式的`test`方法。
<div class='code-example'>
@ -226,15 +335,24 @@ it applies the `test` RegEx patterns.
When a pattern matches the filename, Webpack processes the file with the associated loader.
When a pattern matches the filename, Webpack processes the file with the associated loader.
如果一个模式匹配上文件名Webpack就用它所关联的加载器处理这个文件。
The first `import` file matches the `.ts` pattern so Webpack processes it with the `awesome-typescript-loader`.
The imported file doesn't match the second pattern so its loader is ignored.
The imported file doesn't match the second pattern so its loader is ignored.
The second `import` matches the second `.css` pattern for which you have *two* loaders chained by the (!) character.
Webpack applies chained loaders *right to left*. So it applies
the `css` loader first to flatten CSS `@import` and `url(...)` statements.
Then it applies the `style` loader to append the css inside `<style>` elements on the page.
第一个`import`文件匹配上了`.ts`模式于是Webpack就用`awesome-typescript-loader`加载器处理它。
导入的文件没有匹配上第二个模式,于是它的加载器就被忽略了。
The second `import` matches the second `.css` pattern for which you have *two* loaders chained by the (!) character.
Webpack applies chained loaders *right to left* . So it applies
the `css` loader first to flatten CSS `@import` and `url(...)` statements.
Then it applies the `style` loader to append the css inside `<style>` elements on the page.
第二个`import`匹配上了第二个`.css`模式,它有两个用叹号字符(`!`)串联起来的加载器。
Webpack会*从右到左*逐个应用串联的加载器,于是它先应用了`css`加载器(用来平面化CSS的`@import`和`url(...)`语句)
然后应用了`style`加载器(用来把css追加到页面上的*&lt;style&gt;*元素中)。
{@a plugins}
@ -243,9 +361,14 @@ Then it applies the `style` loader to append the css inside `<style>` elements o
### Plugins
### 插件
Webpack has a build pipeline with well-defined phases.
Tap into that pipeline with plugins such as the `uglify` minification plugin:
Webpack有一条构建流水线它被划分成多个经过精心定义的阶段(phase)。
我们可以把插件(比如`uglify`代码最小化插件)挂到流水线上:
<div class='code-example'>
@ -266,12 +389,21 @@ Tap into that pipeline with plugins such as the `uglify` minification plugin:
## Configuring Webpack
After that brief orientation, you are ready to build your own Webpack configuration for Angular apps.
## 配置Webpack
After that brief orientation, you are ready to build your own Webpack configuration for Angular apps.
经过简短的培训之后我们准备为Angular应用构建一份自己的Webpack配置了。
Begin by setting up the development environment.
从设置开发环境开始。
Create a new project folder.
创建一个新的项目文件夹。
<code-example language="sh" class="code-shell">
mkdir angular-webpack
cd angular-webpack
@ -280,7 +412,9 @@ Create a new project folder.
Add these files:
Add these files :
把下列文件添加到根目录下:
<code-tabs>
@ -317,9 +451,13 @@ Many of these files should be familiar from other Angular documentation guides,
especially the [Typescript configuration](guide/typescript-configuration) and
[npm packages](guide/npm-packages) guides.
这些文件很多都很眼熟,它们在其他文档里已经出现过,特别是[_TypeScript配置_](guide/typescript-configuration)和[_npm包_](guide/npm-packages)这两章里。
Webpack, the plugins, and the loaders are also installed as packages.
They are listed in the updated `packages.json`.
Webpack包括它的插件以及加载器也是以npm包的形式安装的它们也列在了修改后的 package.json 中。
</div>
@ -327,6 +465,9 @@ They are listed in the updated `packages.json`.
Open a terminal window and install the npm packages.
打开命令行窗口并安装这些*npm*包
<code-example language="sh" class="code-shell">
npm install
@ -343,9 +484,13 @@ Open a terminal window and install the npm packages.
You'll need polyfills to run an Angular application in most browsers as explained
in the [Browser Support](guide/browser-support) guide.
我们在[_浏览器支持_](guide/browser-support)章节里解释过Angular应用要能在大多数的浏览器里运行它还需要一些polyfills。
Polyfills should be bundled separately from the application and vendor bundles.
Add a `polyfills.ts` like this one to the `src/` folder.
Polyfills最好跟应用代码和vendor代码区分开来单独打包所以我们需要在`src/`文件夹里添加一个`polyfills.ts`文件,代码如下:
<code-example path="webpack/src/polyfills.ts" title="src/polyfills.ts" linenums="false">
@ -365,6 +510,8 @@ Add a `polyfills.ts` like this one to the `src/` folder.
Load `zone.js` early within `polyfills.ts`, immediately after the other ES6 and metadata shims.
`polyfills.ts`文件里,`zone.js`库须尽早引入紧跟在ES6 shims和metadata shims之后。
</div>
@ -373,6 +520,8 @@ Load `zone.js` early within `polyfills.ts`, immediately after the other ES6 and
Because this bundle file will load first, `polyfills.ts` is also a good place to configure the browser environment
for production or development.
由于这个包最先加载,所以`polyfills.ts`非常适合用来配置浏览器环境,如生产环境配置或是开发环境。
{@a common-configuration}
@ -380,11 +529,17 @@ for production or development.
### Common configuration
### 通用配置
Developers typically have separate configurations for development, production, and test environments.
All three have a lot of configuration in common.
开发、生产、测试等不同的环境通常会分开配置,但实际上这些配置也有很多地方是通用的。
Gather the common configuration in a file called `webpack.common.js`.
我们可以把这些通用的配置收归到一个文件,命名为`webpack.common.js`。
<code-example path="webpack/config/webpack.common.js" title="config/webpack.common.js" linenums="false">
@ -396,16 +551,34 @@ Gather the common configuration in a file called `webpack.common.js`.
### Inside _webpack.common.js_
### webpack.common.js解读
Webpack is a NodeJS-based tool that reads configuration from a JavaScript commonjs module file.
Webpack是基于NodeJS的一个工具它能够从一个*commonjs*规范的JavaScript模块文件里读取配置。
The configuration imports dependencies with `require` statements
and exports several objects as properties of a `module.exports` object.
这个配置文件是通过`require`语句导入依赖,然后将多个对象作为`module.exports`对象的属性导出。
* [`entry`](guide/webpack#common-entries)&mdash;the entry-point files that define the bundles.
[`entries`](guide/webpack#common-entries) - 包体的入口文件。
* [`resolve`](guide/webpack#common-resolve)&mdash;how to resolve file names when they lack extensions.
[`resolve`](guide/webpack#common-resolve) - 省略扩展名时如何解释文件名。
* [`module.rules`](guide/webpack#common-rules)&mdash; `module` is an object with `rules` for deciding how files are loaded.
[`module.rules`](guide/webpack#common-rules) - `module`是一个对象,里面的`rules`属性用来决定文件如何加载。
* [`plugins`](guide/webpack#common-plugins)&mdash;creates instances of the plugins.
[`plugins`](guide/webpack#common-plugins) - 创建插件的实例。
{@a common-entries}
@ -414,6 +587,8 @@ and exports several objects as properties of a `module.exports` object.
The first export is the `entry` object:
如上所述,第一个导出的对象是*entries*
<code-example path="webpack/config/webpack.common.js" region="entries" title="config/webpack.common.js" linenums="false">
@ -423,19 +598,33 @@ The first export is the `entry` object:
This `entry` object defines the three bundles:
`entry`对象定义了三个包:
* `polyfills`&mdash;the polyfills needed to run Angular applications in most modern browsers.
`polyfills` - 使得Angular应用能够运行在大多数的现代浏览器。
* `vendor`&mdash;the third-party dependencies such as Angular, lodash, and bootstrap.css.
`vendor` - 第三方依赖如Angular、lodash和bootstrap.css。
* `app`&mdash;the application code.
`app` - 应用代码。
{@a common-resolve}
#### _resolve_ extension-less imports
#### _resolve_ 无扩展名的文件导入
The app will `import` dozens if not hundreds of JavaScript and TypeScript files.
You could write `import` statements with explicit extensions like this example:
如果你的应用程序只须`import`几十个JavaScript或TypeScript文件而不是几百个你可以在`import`语句里完整写上扩展名,如:
<div class='code-example'>
@ -452,6 +641,8 @@ But most `import` statements don't mention the extension at all.
Tell Webpack to resolve extension-less file requests by looking for matching files with
`.ts` extension or `.js` extension (for regular JavaScript files and pre-compiled TypeScript files).
但实际上大部分`import`语句都不带扩展名我们可以告诉Webpack在查找这些没有扩展名的文件时自动加上`.ts`或者`.js`扩展名来匹配。
<code-example path="webpack/config/webpack.common.js" region="resolve" title="config/webpack.common.js" linenums="false">
@ -466,6 +657,8 @@ Tell Webpack to resolve extension-less file requests by looking for matching fil
If Webpack should resolve extension-less files for styles and HTML,
add `.css` and `.html` to the list.
如果我们希望Webapck也能解析不带扩展名的样式和HTML文件在列表里追加`.css`和`.html`即可。
</div>
@ -477,8 +670,11 @@ add `.css` and `.html` to the list.
#### _module.rules_
Rules tell Webpack which loaders to use for each file, or module:
Rules用来告诉Webpack加载不同文件或模块时该用哪个加载器。
<code-example path="webpack/config/webpack.common.js" region="loaders" title="config/webpack.common.js" linenums="false">
@ -487,12 +683,27 @@ Rules tell Webpack which loaders to use for each file, or module:
* `awesome-typescript-loader`&mdash;a loader to transpile the Typescript code to ES5, guided by the `tsconfig.json` file.
`awesome-typescript-loader` - 一个用于把TypeScript代码转译成ES5的加载器它会由`tsconfig.json`文件提供指导
* `angular2-template-loader`&mdash;loads angular components' template and styles.
`angular2-template-loader` - 用于加载Angular组件的模板和样式
* `html-loader`&mdash;for component templates.
`html-loader` - 为组件模板准备的加载器
* images/fonts&mdash;Images and fonts are bundled as well.
`images/fonts` - 图片和字体文件也能被打包。
* CSS&mdash;the first pattern matches application-wide styles; the second handles
component-scoped styles (the ones specified in a component's `styleUrls` metadata property).
CSS - 第一个模式匹配应用级样式,第二个模式匹配组件局部样式(就是在组件元数据的`styleUrls`属性中指定的那些)。
<div class="l-sub-section">
@ -501,9 +712,15 @@ The first pattern is for the application-wide styles. It excludes `.css` files w
where the component-scoped styles sit. The `ExtractTextPlugin` (described below) applies the `style` and `css`
loaders to these files.
第一个模式是给全局样式使用的,它排除了`/src/app`目录下的`.css`文件,因为那里放着我们的组件局部样式。
它只包含了那些位于`/src/app`及其上级目录的`.css`文件,那里是应用级样式。
`ExtractTextPlugin`(后面会讲到)使用`style`和`css`加载器来处理这些文件。
The second pattern filters for component-scoped styles and loads them as strings via the `raw-loader`,
which is what Angular expects to do with styles specified in a `styleUrls` metadata property.
第二个模式过滤器是给组件局部样式的,并通过`raw`加载器把它们加载成字符串 —— 那是Angular期望通过元数据的`styleUrls`属性来指定样式的形式。
</div>
@ -515,6 +732,8 @@ which is what Angular expects to do with styles specified in a `styleUrls` metad
Multiple loaders can be chained using the array notation.
多重加载器也能使用数组形式串联起来。
</div>
@ -526,8 +745,11 @@ Multiple loaders can be chained using the array notation.
#### _plugins_
Finally, create instances of three plugins:
最后,创建三个插件实例:
<code-example path="webpack/config/webpack.common.js" region="plugins" title="config/webpack.common.js" linenums="false">
@ -540,11 +762,18 @@ Finally, create instances of three plugins:
#### *CommonsChunkPlugin*
The `app.js` bundle should contain only application code. All vendor code belongs in the `vendor.js` bundle.
The `app.js` bundle should contain only application code. All vendor code belongs in the `vendor.js` bundle.
`app.js`包应该只包含应用代码。所有第三方代码都应该放进`vendor.js`包中。
Of course the application code imports vendor code.
On its own,Webpack is not smart enough to keep the vendor code out of the `app.js` bundle.
The `CommonsChunkPlugin` does that job.
当然,应用代码中还是要`imports`第三方代码。
Webpack还没有智能到自动把提供商代码排除在`app.js`包之外的程度。
`CommonsChunkPlugin`插件能完成此工作。
Of course the application code imports vendor code.
On its own, Webpack is not smart enough to keep the vendor code out of the `app.js` bundle.
The `CommonsChunkPlugin` does that job.
<div class="l-sub-section">
@ -554,6 +783,10 @@ The `CommonsChunkPlugin` identifies the hierarchy among three _chunks_: `app` ->
Where Webpack finds that `app` has shared dependencies with `vendor`, it removes them from `app`.
It would remove `polyfills` from `vendor` if they shared dependencies, which they don't.
`CommonsChunkPlugin`标记出了三个_块_之间的等级体系`app` -> `vendor` -> `polyfills`
当Webpack发现`app`与`vendor`有共享依赖时,就把它们从`app`中移除。
在`vendor`和`polyfills`之间有共享依赖时也同样如此(虽然它们没啥可共享的)。
</div>
@ -568,6 +801,10 @@ Webpack generates a number of js and CSS files.
You _could_ insert them into the `index.html` _manually_. That would be tedious and error-prone.
Webpack can inject those scripts and links for you with the `HtmlWebpackPlugin`.
Webpack生成了一些js和css文件。
虽然我们_可以手动_把它们插入到`index.html`中,但那样既枯燥又容易出错。
Webpack可以通过`HtmlWebpackPlugin`自动为我们注入那些`script`和`link`标签。
{@a environment-configuration}
@ -575,12 +812,19 @@ Webpack can inject those scripts and links for you with the `HtmlWebpackPlugin`.
### Environment-specific configuration
The `webpack.common.js` configuration file does most of the heavy lifting.
### 环境相关的配置
The `webpack.common.js` configuration file does most of the heavy lifting.
Create separate, environment-specific configuration files that build on `webpack.common`
by merging into it the peculiarities particular to the target environments.
`webpack.common.js`配置做了大部分繁重的工作。
通过合并它们特有的配置,我们可以基于`webpack.common`为目标环境创建独立的、环境相关的配置文件。
These files tend to be short and simple.
这些文件越小越简单越好。
{@a development-configuration}
@ -588,7 +832,11 @@ These files tend to be short and simple.
### Development configuration
Here is the `webpack.dev.js` development configuration file.
### 开发环境配置
Here is the `webpack.dev.js` development configuration file.
下面是开发环境的而配置文件`webpack.dev.js`
<code-example path="webpack/config/webpack.dev.js" title="config/webpack.dev.js" linenums="false">
@ -599,22 +847,35 @@ Here is the `webpack.dev.js` development configuration file.
The development build relies on the Webpack development server, configured near the bottom of the file.
开发环境下的构建依赖于Webpack的开发服务器我们在靠近文件底部的地方配置了它。
Although you tell Webpack to put output bundles in the `dist` folder,
the dev server keeps all bundles in memory; it doesn't write them to disk.
You won't find any files in the `dist` folder, at least not any generated from *this development build*.
You won't find any files in the `dist` folder ,at least not any generated from *this development build*.
虽然我们告诉Webpack把输出包放到`dist`目录,但实际上开发服务器把这些包都放在了内存里,而不会把它们写到硬盘中。
所以在`dist`目录下是找不到任何文件的(至少现在这个开发环境下构建时没有)。
The `HtmlWebpackPlugin`, added in `webpack.common.js`, uses the `publicPath` and the `filename` settings to generate
The `HtmlWebpackPlugin` ,added in `webpack.common.js`, uses the `publicPath` and the `filename` settings to generate
appropriate `<script>` and `<link>` tags into the `index.html`.
`HtmlWebpackPlugin`(由`webpack.common.js`引入)插件使用了*`publicPath`*和*`filename`*设置,
来向`index.html`中插入适当的&lt;script&gt;&lt;link&gt;标签。
The CSS styles are buried inside the Javascript bundles by default. The `ExtractTextPlugin` extracts them into
external `.css` files that the `HtmlWebpackPlugin` inscribes as `<link>` tags into the `index.html`.
Refer to the [Webpack documentation](https://webpack.github.io/docs/) for details on these and
other configuration options in this file.
默认情况下我们这些CSS样式会被埋没在JavaScript包中。`ExtractTextPlugin`会把它们提取成外部`.css`文件,
这样`HtmlWebpackPlugin`插件就会转而把一个&lt;link&gt;标签写进`index.html`了。
Refer to the [Webpack documentation](https://webpack.github.io/docs/) for details on these and other configuration options in this file.
要了解本文件中这些以及其它配置项的详情,请参阅[Webpack文档](https://webpack.github.io/docs/)。
Grab the app code at the end of this guide and try:
抓取本指南底部的应用代码,并试一试:
<code-example language="sh" class="code-shell">
npm start
@ -629,7 +890,11 @@ Grab the app code at the end of this guide and try:
### Production configuration
Configuration of a *production* build resembles *development* configuration with a few key changes.
### 产品环境配置
Configuration of a *production* build resembles *development* configuration with a few key changes.
*产品环境*下的配置和*开发环境*下的配置很相似……除了一些关键的改动。
<code-example path="webpack/config/webpack.prod.js" title="config/webpack.prod.js" linenums="false">
@ -641,21 +906,47 @@ Configuration of a *production* build resembles *development* configuration with
You'll deploy the application and its dependencies to a real production server.
You won't deploy the artifacts needed only in development.
我们希望把应用程序及其依赖都部署到一个真实的产品服务器中。
而不希望部署那些只在开发环境下才用得到的依赖。
Put the production output bundle files in the `dist` folder.
把产品环境的输出包放在`dist`目录下。
Webpack generates file names with cache-busting hash.
Thanks to the `HtmlWebpackPlugin`, you don't have to update the `index.html` file when the hash changes.
Webpack生成的文件名中带有“缓存无效哈希(cache-busting hash)”。
感谢`HtmlWebpackPlugin`插件,当这些哈希值变化时,我们不用去更新`index.html`了。
There are additional plugins:
* *`NoEmitOnErrorsPlugin`&mdash;stops the build if there is an error.
* *`UglifyJsPlugin`&mdash;minifies the bundles.
* *`ExtractTextPlugin`&mdash;extracts embedded css as external files, adding cache-busting hash to the filename.
* *`DefinePlugin`&mdash;use to define environment variables that you can reference within the application.
* *`LoaderOptionsPlugins`&mdash;to override options of certain loaders.
还有一些别的插件:
* *`NoEmitOnErrorsPlugin`&mdash; stops the build if there is an error.
*`NoEmitOnErrorsPlugin`* - 如果出错就停止构建。*
*`UglifyJsPlugin`&mdash; minifies the bundles.
`UglifyJsPlugin` - 最小化(minify)生成的包。
* *`ExtractTextPlugin`&mdash; extracts embedded css as external files, adding cache-busting hash to the filename.
*`ExtractTextPlugin`* - 把内嵌的css抽取成外部文件并为其文件名添加“缓存无效哈希”。
* *`DefinePlugin`&mdash; use to define environment variables that you can reference within the application.
*`DefinePlugin`* - 用来定义环境变量,以便我们在自己的程序中引用它。
* *`LoaderOptionsPlugins`&mdash; to override options of certain loaders.
*`LoaderOptionsPlugins`* - 为特定的加载器提供选项。
Thanks to the `DefinePlugin` and the `ENV` variable defined at top, you can enable Angular production mode like this:
感谢*DefinePlugin*和顶部定义的`ENV`变量我们就可以像这样启用Angular的产品模式了
<code-example path="webpack/src/main.ts" region="enable-prod" title="src/main.ts" linenums="false">
@ -665,6 +956,8 @@ Thanks to the `DefinePlugin` and the `ENV` variable defined at top, you can enab
Grab the app code at the end of this guide and try:
抓取本指南底部的应用代码,并试一试:
<code-example language="sh" class="code-shell">
npm run build
@ -679,14 +972,23 @@ Grab the app code at the end of this guide and try:
### Test configuration
### 测试环境配置
You don't need much configuration to run unit tests.
You don't need the loaders and plugins that you declared for your development and production builds.
You probably don't need to load and process the application-wide styles files for unit tests and doing so would slow you down;
you'll use the `null` loader for those CSS files.
我们并不需要使用很多配置项来运行单元测试。
也不需要在开发环境和产品环境下引入的那些加载器和插件。
如果有可能拖慢执行速度,甚至都不需要在单元测试中加载和处理应用全局样式文件,所以我们用一个`null`加载器来处理所有CSS。
You could merge the test configuration into the `webpack.common` configuration and override the parts you don't want or need.
But it might be simpler to start over with a completely fresh configuration.
我们可以把测试环境的配置合并到`webpack.common`配置中,并且改写不想要或不需要的部分。
但是从一个全新的配置开始可能更简单。
<code-example path="webpack/config/webpack.test.js" title="config/webpack.test.js" linenums="false">
@ -696,6 +998,8 @@ But it might be simpler to start over with a completely fresh configuration.
Reconfigure [Karma](https://karma-runner.github.io/1.0/index.html) to use Webpack to run the tests:
重新配置[Karma](https://karma-runner.github.io/1.0/index.html)让它使用webpack来运行这些测试
<code-example path="webpack/config/karma.conf.js" title="config/karma.conf.js" linenums="false">
@ -706,9 +1010,14 @@ Reconfigure [Karma](https://karma-runner.github.io/1.0/index.html) to use Webpac
You don't precompile the TypeScript; Webpack transpiles the Typescript files on the fly, in memory, and feeds the emitted JS directly to Karma.
There are no temporary files on disk.
The `karma-test-shim` tells Karma what files to pre-load and
我们不用预编译TypeScriptWebpack随时在内存中转译我们的TypeScript文件并且把产出的JS直接反馈给Karma。
硬盘上没有任何临时文件。
The `karma-test-shim` tells Karma what files to pre-load and
primes the Angular test framework with test versions of the providers that every app expects to be pre-loaded.
`karma-test-shim`告诉Karma哪些文件需要预加载首要的是带有“测试版提供商”的Angular测试框架是每个应用都希望预加载的。
<code-example path="webpack/config/karma-test-shim.js" title="config/karma-test-shim.js" linenums="false">
@ -721,9 +1030,16 @@ You tell Webpack to find and load the test files (the files ending in `.spec.ts`
Each spec file imports all&mdash;and only&mdash;the application source code that it tests.
Webpack loads just _those_ specific application files and ignores the other files that you aren't testing.
注意我们_并没有_明确加载这些应用代码。
只是告诉Webpack查找并加载我们的测试文件(文件名以`.spec.ts`结尾)。
每个规约(spec)文件都导入了所有(也只有)它测试所需的应用源码。
Webpack只加载_那些_特定的应用文件而忽略所有其它我们不会测试到的。
Grab the app code at the end of this guide and try:
抓取本指南底部的应用代码,并试一试:
<code-example language="sh" class="code-shell">
npm test
@ -734,9 +1050,13 @@ Grab the app code at the end of this guide and try:
## Trying it out
## 试一试
Here is the source code for a small application that bundles with the
Webpack techniques covered in this guide.
这里是一个小型应用的全部源码我们可以用本章中学到的Webpack技术打包它们。
<code-tabs>
@ -788,12 +1108,20 @@ The <code>app.component.html</code> displays this downloadable Angular logo
Create a folder called `images` under the project's `assets` folder, then right-click (Cmd+click on Mac)
on the image and download it to that folder.
<code>app.component.html</code>显示了这个可下载的Angular Logo
<a href="assets/images/logos/angular2/angular.png" target="_blank">
<img src="assets/images/logos/angular2/angular.png" height="40px" title="download Angular logo"></a>
在项目的`assets`目录下创建一个名叫`images`的文件夹然后右键点击Mac上是Cmd+点击)本图片,并把它下载到`images`文件夹中。
{@a bundle-ts}
Here again are the TypeScript entry-point files that define the `polyfills` and `vendor` bundles.
这里又是TypeScript的入口点文件它定义了`polyfills`和`vendor`这两个包。
<code-tabs>
<code-pane title="src/polyfills.ts" path="webpack/src/polyfills.ts">
@ -810,26 +1138,47 @@ Here again are the TypeScript entry-point files that define the `polyfills` and
### Highlights
* There are no `<script>` or `<link>` tags in the `index.html`.
### 重点:
* There are no `<script>` or `<link>` tags in the `index.html`.
The `HtmlWebpackPlugin` inserts them dynamically at runtime.
在`index.html`中没有&lt;script&gt;&lt;link&gt;标签。
`HtmlWebpackPlugin`会在运行时动态插入它们。
* The `AppComponent` in `app.component.ts` imports the application-wide css with a simple `import` statement.
`app.component.ts`中的`AppComponent`类简单的用一个`import`语句导入了应用级css。
* The `AppComponent` itself has its own html template and css file. WebPack loads them with calls to `require()`.
Webpack stashes those component-scoped files in the `app.js` bundle too.
You don't see those calls in the source code;
they're added behind the scenes by the `angular2-template-loader` plug-in.
You don't see those calls in the source code;
they're added behind the scenes by the `angular2-template-loader` plug-in.
`AppComponent`组件本身有它自己的HTML模板和CSS文件。Webpack通过调用`require()`方法加载它们。Webpack还把那些组件内部的文件打包进了`app.js`中。
我们在自己的源码中看不到这些调用,这些工作是由幕后的`angular2-template-loader`插件完成的。
* The `vendor.ts` consists of vendor dependency `import` statements that drive the `vendor.js` bundle.
The application imports these modules too; they'd be duplicated in the `app.js` bundle
if the `CommonsChunkPlugin` hadn't detected the overlap and removed them from `app.js`.
The application imports these modules too; they'd be duplicated in the `app.js` bundle
if the `CommonsChunkPlugin` hadn't detected the overlap and removed them from `app.js`.
`vendor.ts`由`import`提供商依赖的语句组成,它最终决定了`vender.js`的内容。
本应用也导入这些模块,如果没有`CommonsChunkPlugin`插件检测出这种重叠,并且把它们从`app.js`中移除,它们就会同时出现在`app.js`包中。
{@a conclusion}
## Conclusion
You've learned just enough Webpack to configurate development, test and production builds
## 总结
You've learned just enough Webpack to configurate development, test and production builds
for a small Angular application.
我们学到了刚好够用来在开发、测试、产品环境下构建一个小型Angular应用的Webpack配置知识。
_You could always do more_. Search the web for expert advice and expand your Webpack knowledge.
[Back to top](guide/webpack#top)
_但我们还能做得更多_。搜索互联网来获得专家的建议并扩展你对Webpack的认识。
[Back to top](guide/webpack#top)
[回到顶部](guide/webpack#top)

View File

@ -1,8 +1,8 @@
@title
Tutorial: Tour of Heroes
教程: 英雄指南
@intro
The Tour of Heroes tutorial takes you through the steps of creating an Angular application in TypeScript.
英雄指南教程带我们一步步使用 TypeScript 创建 Angular 应用。
@description
@ -10,11 +10,16 @@ The Tour of Heroes tutorial takes you through the steps of creating an Angular a
The grand plan for this tutorial is to build an app that helps a staffing agency manage its stable of heroes.
本教程的终极计划是构建一个程序,来帮助招聘公司管理一群英雄。
即使英雄们也需要找工作。
The Tour of Heroes app covers the core fundamentals of Angular. You'll build a basic app that
has many of the features you'd expect to find in a full-blown, data-driven app: acquiring and displaying
a list of heroes, editing a selected hero's detail, and navigating among different
views of heroic data.
这篇《英雄指南》覆盖了 Angular 的核心原理。这次构建的应用会涉及很多特性:获得并显示英雄列表,编辑所选英雄的详情,并在英雄数据的多个视图之间建立导航。这些特性,在成熟的、数据驱动的应用中经常见到。
You'll use built-in directives to show and hide elements and display lists of hero data.
You'll create components to display hero details and show an array of heroes.
You'll use one-way data binding for read-only data. You'll add editable fields to update a model
@ -24,24 +29,42 @@ format data with pipes. You'll create a shared service to assemble the heroes.
And you'll use routing to navigate among different views and their components.
<!-- CF: Should this be a bullet list? -->
我们将使用内置指令来显示 / 隐藏元素,并且显示英雄数据的列表。
我们将创建组件来显示英雄的详情和英雄列表。
我们将对只读数据使用单向数据绑定。我们将添加一些可编辑字段,并通过双向数据绑定更新模型。
我们将把组件上的方法绑定到用户事件上,比如按键和点击。
我们将让用户能从主列表视图中选择一个英雄,然后在详情视图中编辑它。
我们将通过管道对数据进行格式化。
我们将创建一个共享服务来管理我们的英雄们。
我们将使用路由在不同的视图及其组件之间进行导航。
You'll learn enough core Angular to get started and gain confidence that
Angular can do whatever you need it to do.
You'll cover a lot of ground at an introductory level, and you'll find many links
to pages with greater depth.
完成本教程后,我们将学习足够的 Angular 核心技术,并确信 Angular 确实能做到我们需要它做的。
我们将覆盖大量入门级知识,同时我们也会看到大量链接,指向更深入的章节。
When you're done with this tutorial, the app will look like this <live-example name="toh-6"></live-example>.
当完成这个教程时,应用运行起来是这样的:<live-example name="toh-6"></live-example>
## The end game
## 游戏的终点
Here's a visual idea of where this tutorial leads, beginning with the "Dashboard"
view and the most heroic heroes:
下面是本教程关于界面的构想开始是“Dashboard仪表盘”视图来展示我们最勇敢的英雄。
<figure class='image-display'>
<img src='assets/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard"></img>
<img src='assets/images/devguide/toh/heroes-dashboard-1.png' alt="英雄仪表盘的输出"></img>
</figure>
@ -49,12 +72,17 @@ view and the most heroic heroes:
You can click the two links above the dashboard ("Dashboard" and "Heroes")
to navigate between this Dashboard view and a Heroes view.
仪表盘顶部中有两个链接“Dashboard仪表盘”和“Heroes英雄列表”。
我们将点击它们在“仪表盘”和“英雄列表”视图之间导航。
If you click the dashboard hero "Magneta," the router opens a "Hero Details" view
where you can change the hero's name.
当我们点击仪表盘上名叫“Magneta”的英雄时路由将把我们带到这个英雄的详情页在这里我们可以修改英雄的名字。
<figure class='image-display'>
<img src='assets/images/devguide/toh/hero-details-1.png' alt="Details of hero in app"></img>
<img src='assets/images/devguide/toh/hero-details-1.png' alt="英雄详情的输出"></img>
</figure>
@ -63,32 +91,44 @@ Clicking the "Back" button returns you to the Dashboard.
Links at the top take you to either of the main views.
If you click "Heroes," the app displays the "Heroes" master list view.
点击“Back后退”按钮将返回到“Dashboard仪表盘”。
顶部的链接可以把我们带到任何一个主视图。
如果我们点击“Heroes英雄列表”链接应用将把我们带到“英雄”主列表视图。
<figure class='image-display'>
<img src='assets/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app"></img>
<img src='assets/images/devguide/toh/heroes-list-2.png' alt="英雄列表的输出"></img>
</figure>
When you click a different hero name, the read-only mini detail beneath the list reflects the new choice.
当我们点击另一位英雄时,一个只读的“微型详情视图”会显示在列表下方,以体现我们的选择。
You can click the "View Details" button to drill into the
editable details of the selected hero.
我们可以点击“View Details查看详情”按钮进入所选英雄的编辑视图。
The following diagram captures all of the navigation options.
下面这张图汇总了我们所有可能的导航路径。
<figure class='image-display'>
<img src='assets/images/devguide/toh/nav-diagram.png' alt="View navigations"></img>
<img src='assets/images/devguide/toh/nav-diagram.png' alt="查看导航"></img>
</figure>
Here's the app in action:
下图演示了我们应用中的所有操作。
<figure class='image-display'>
<img src='assets/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action"></img>
<img src='assets/images/devguide/toh/toh-anim.gif' alt="英雄指南的所有动作"></img>
</figure>
@ -96,8 +136,15 @@ Here's the app in action:
## Up next
## 接下来
You'll build the Tour of Heroes app, step by step.
Each step is motivated with a requirement that you've likely
met in many applications. Everything has a reason.
Along the way, you'll become familiar with many of the core fundamentals of Angular.
让我们一起一步步构建出《英雄指南》。
正如我们在无数应用遇到那样,每一步都由一个需求驱动。毕竟做任何事都要有个理由。
Along the way, you'll become familiar with many of the core fundamentals of Angular.
这一路上,我们将遇到很多 Angular 核心原理。

View File

@ -1,18 +1,25 @@
@title
The Hero Editor
英雄编辑器
@intro
Build a simple hero editor.
构建一个简单的英雄编辑器
@description
## Setup to develop locally
## 为本地开发搭建环境
Follow the [setup](guide/setup) instructions for creating a new project
named <code>angular-tour-of-heroes</code>.
根据[开发环境](guide/setup)中的说明创建一个名为<ngio-ex path="angular-tour-of-heroes"></ngio-ex>的新项目
The file structure should look like this:
该项目的文件结构应该是这样的:
<div class='filetree'>
@ -82,13 +89,20 @@ The file structure should look like this:
When you're done with this page, the app should look like this <live-example></live-example>.
在我们完成本章时,得到的应用和这个<live-example></live-example>一样。
{@a keep-transpiling}
## Keep the app transpiling and running
## 保持应用不断转译和运行
Enter the following command in the terminal window:
在命令行窗口中输入以下命令:
<code-example language="sh" class="code-shell">
npm start
@ -100,14 +114,24 @@ Enter the following command in the terminal window:
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
这个命令会在“监听”模式下运行TypeScript编译器当代码变化时它会自动重新编译。
同时,该命令还会在浏览器中启动该应用,并且当代码变化时刷新浏览器。
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
在后续构建《英雄指南》过程中,应用能持续运行,而不用中断服务来编译或刷新浏览器。
## Show the hero
## 显示此英雄
Add two properties to the `AppComponent`: a `title` property for the app name and a `hero` property
for a hero named "Windstorm."
往`AppComponent`中添加两个属性:`title`属性用来表示应用的名字,而`hero`属性用来表示名叫“Windstorm”的英雄。
<code-example path="toh-1/app/app.component.1.ts" region="app-component-1" title="app.component.ts (AppComponent class)" linenums="false">
@ -117,6 +141,8 @@ for a hero named "Windstorm."
Now update the template in the `@Component` decorator with data bindings to these new properties.
下面,更新`@Component`装饰器中指定的模板,为这些新属性建立数据绑定。
<code-example path="toh-1/app/app.component.1.ts" region="show-hero" title="app.component.ts (@Component)" linenums="false">
@ -126,10 +152,14 @@ Now update the template in the `@Component` decorator with data bindings to thes
The browser refreshes and displays the title and hero name.
保存后,浏览器应自动刷新,显示标题和英雄。
The double curly braces are Angular's *interpolation binding* syntax.
These interpolation bindings present the component's `title` and `hero` property values,
as strings, inside the HTML header tags.
这里的双大括号是Angular中的*插值表达式绑定*语法。它们表示组件的`title`和`hero`属性的值会作为字符串插入到HTML标签中间。
<div class="l-sub-section">
@ -137,6 +167,8 @@ as strings, inside the HTML header tags.
Read more about interpolation in the [Displaying Data](guide/displaying-data) page.
要了解插值表达式的更多知识,见[显示数据](guide/displaying-data)。
</div>
@ -144,12 +176,20 @@ Read more about interpolation in the [Displaying Data](guide/displaying-data) pa
### Hero object
### Hero 对象
The hero needs more properties.
Convert the `hero` from a literal string to a class.
显然,英雄还需要更多属性。
让我们把`hero`从一个字符串字面量换成一个类。
Create a `Hero` class with `id` and `name` properties.
Add these properties near the top of the `app.component.ts` file, just below the import statement.
创建一个`Hero`类,它具有`id`和`name`属性。
现在,把下列代码放在`app.component.ts`的顶部,仅次于 import 语句。
<code-example path="toh-1/src/app/app.component.ts" region="hero-class-1" title="src/app/app.component.ts (Hero class)" linenums="false">
@ -160,6 +200,9 @@ Add these properties near the top of the `app.component.ts` file, just below the
In the `AppComponent` class, refactor the component's `hero` property to be of type `Hero`,
then initialize it with an `id` of `1` and the name `Windstorm`.
现在,有了一个`Hero`类,我们把组件`hero`属性的类型换成`Hero`。
然后以`1`为 id、以 “Windstorm” 为名字,初始化它。
<code-example path="toh-1/src/app/app.component.ts" region="hero-property-1" title="src/app/app.component.ts (hero property)" linenums="false">
@ -170,6 +213,8 @@ then initialize it with an `id` of `1` and the name `Windstorm`.
Because you changed the hero from a string to an object,
update the binding in the template to refer to the hero's `name` property.
我们把`hero`从一个字符串换成了对象,所以也得更新模板中的绑定表达式,来引用`hero`的`name`属性。
<code-example path="toh-1/app/app.component.1.ts" region="show-hero-2" title="toh-1/app/app.component.ts">
@ -179,16 +224,26 @@ update the binding in the template to refer to the hero's `name` property.
The browser refreshes and continues to display the hero's name.
浏览器自动刷新,并继续显示这位英雄的名字。
### Adding HTML with multi-line template strings
### 使用多行模板字符串添加更多 HTML
To show all of the hero's properties,
add a `<div>` for the hero's `id` property and another `<div>` for the hero's `name`.
To keep the template readable, place each `<div>` on its own line.
要显示英雄的所有属性,还要为英雄的`id`属性添加一个`<div>`,为英雄的`name`属性添加另一个`<div>`。
为了保持模板的可读性,把每个`<div>`单独放一行。
The backticks around the component template let you put the `<h1>`, `<h2>`, and `<div>` elements on their own lines,
thanks to the <i>template literals</i> feature in ES2015 and TypeScript. For more information, see
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" target="_blank" title="template literal">Template literals</a>.
反引号包裹的组件模板能让你把`<h1>`、`<h2>`和`<div>`元素各自放在一行上。
感谢ES2015和TypeScript的*模板字面量*特性。要了解更多,请参见<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" target="_blank" title="template literal">模板字面量Template literals</a> 页。
<code-example path="toh-1/app/app.component.1.ts" region="multi-line-strings" title="app.component.ts (AppComponent's template)" linenums="false">
@ -200,16 +255,28 @@ thanks to the <i>template literals</i> feature in ES2015 and TypeScript. For mor
## Edit the hero name
## 编辑英雄名字
Users should be able to edit the hero name in an `<input>` textbox.
The textbox should both _display_ the hero's `name` property
and _update_ that property as the user types.
用户应该能在一个`<input>`输入框中编辑英雄的名字。
当用户输入时,这个输入框应该能同时*显示*和*修改*英雄的`name`属性。
You need a two-way binding between the `<input>` form element and the `hero.name` property.
也就是说,我们要在表单元素`<input>`和组件的`hero.name`属性之间建立双向绑定。
### Two-way binding
### 双向绑定
Refactor the hero name in the template so it looks like this:
把模板中的英雄名字重构成这样:
<code-example path="toh-1/app/app.component.1.ts" region="name-input" title="toh-1/app/app.component.ts" linenums="false">
</code-example>
@ -221,22 +288,40 @@ to the textbox.
Data flows _in both directions:_ from the property to the textbox,
and from the textbox back to the property.
`[(ngModel)]`是一个Angular语法用与把`hero.name`绑定到输入框中。
它的数据流是*双向的*:从属性到输入框,并且从输入框回到属性。
Unfortunately, immediately after this change, the application breaks.
If you looked in the browser console, you'd see Angular complaining that
"`ngModel` ... isn't a known property of `input`."
不幸的是,做了这项改动之后,我们的程序崩溃了。
打开浏览器的控制台我们会看到Angular抱怨说“`ngModel` ... isn't a known property of `input`.”(`ngModel`不是`input`元素的已知属性)
Although `NgModel` is a valid Angular directive, it isn't available by default.
It belongs to the optional `FormsModule`.
You must opt-in to using that module.
虽然`NgModel`是一个有效的Angular指令但它默认情况下却是不可用的。
它属于一个可选模块`FormsModule`。
我们必须选择使用那个模块。
### Import the _FormsModule_
### 导入 _FormsModule_
Open the `app.module.ts` file and import the `FormsModule` symbol from the `@angular/forms` library.
Then add the `FormsModule` to the `@NgModule` metadata's `imports` array, which contains the list
of external modules that the app uses.
打开`app.module.ts`文件,并且从`@angular/forms`库中导入符号`FormsModule`。
然后把`FormsModule`添加到`@NgModule`元数据的`imports`数组中,它是当前应用正在使用的外部模块列表。
The updated `AppModule` looks like this:
修改后的`AppModule`是这样的:
<code-example path="toh-1/src/app/app.module.ts" title="app.module.ts (FormsModule import)">
</code-example>
@ -253,6 +338,9 @@ Read more about `FormsModule` and `ngModel` in the
[Two-way binding with NgModel](guide/template-syntax#ngModel) section of the
[Template Syntax](guide/template-syntax) guide.
要学习关于`FormsModule`和`ngModel`的更多知识,参见[表单](guide/forms#ngModel)和
[模板语法](guide/template-syntax#ngModel)。
</div>
@ -261,23 +349,44 @@ Read more about `FormsModule` and `ngModel` in the
When the browser refreshes, the app should work again.
You can edit the hero's name and see the changes reflected immediately in the `<h2>` above the textbox.
浏览器刷新。又见到我们的英雄了。我们可以编辑英雄的名字,也能看到这个改动立刻体现在`<h2>`中。
## The road you've travelled
## 我们已经走过的路
Take stock of what you've built.
我们来盘点一下已经构建完成的部分。
* The Tour of Heroes app uses the double curly braces of interpolation (a type of one-way data binding)
to display the app title and properties of a `Hero` object.
我们的《英雄指南》使用双大括号插值表达式(单向数据绑定的一种形式)来显示应用的标题和`Hero`对象的属性。
* You wrote a multi-line template using ES2015's template literals to make the template readable.
我们使用 ES2015 的模板字符串写了一个多行模板,使我们的模板更具可读性。
* You added a two-way data binding to the `<input>` element
using the built-in `ngModel` directive. This binding both displays the hero's name and allows users to change it.
为了同时显示和修改英雄的名字,我们还使用了内置的`ngModel`指令,往`<input>`元素上添加了双向数据绑定。
* The `ngModel` directive propagates changes to every other binding of the `hero.name`.
`ngModel`指令将这些修改传播到每一个对`hero.name`的其它绑定。
Your app should look like this <live-example></live-example>.
运行这部分的<live-example></live-example>
Here's the complete `app.component.ts` as it stands now:
完整的`app.component.ts`是这样的:
<code-example path="toh-1/src/app/app.component.ts" title="src/app/app.component.ts">
@ -287,6 +396,13 @@ Here's the complete `app.component.ts` as it stands now:
## The road ahead
## 前方的路
In the [next tutorial page](tutorial/toh-pt2), you'll build on the Tour of Heroes app to display a list of heroes.
You'll also allow the user to select heroes and display their details.
You'll learn more about how to retrieve lists and bind them to the template.
You'll learn more about how to retrieve lists and bind them to the template.
在[教程的下一章](tutorial/toh-pt2),我们将在这个《英雄指南》中显示一个英雄列表。
我们将允许允许用户选择一个英雄,并且显示它/她的详情。
我们还将学会如何获取列表以及将它们绑定到模板中。

View File

@ -1,8 +1,8 @@
@title
Master/Detail
主从结构
@intro
Build a master/detail page with a list of heroes.
构建一个主从结构的页面,用于展现英雄列表
@description
@ -10,15 +10,25 @@ Build a master/detail page with a list of heroes.
In this page, you'll expand the Tour of Heroes app to display a list of heroes, and
allow users to select a hero and display the hero's details.
我们需要管理多个英雄。我们将扩展《英雄指南》应用,让它显示一个英雄列表,
允许用户选择一个英雄,查看该英雄的详细信息。
When you're done with this page, the app should look like this <live-example></live-example>.
当我们完成本章时,应用应该是这样的:<live-example></live-example>
## Where you left off
## 延续上一步教程
Before you continue with this page of the Tour of Heroes,
verify that you have the following structure after [The Hero Editor](tutorial/toh-pt1) page.
If your structure doesn't match, go back to that page to figure out what you missed.
在继续《英雄指南》的第二部分之前,先来检查一下,完成[第一部分](tutorial/toh-pt1)之后,你是否已经有了如下目录结构。如果没有,你得先回到第一部分,看看错过了哪里。
<div class='filetree'>
@ -87,8 +97,13 @@ If your structure doesn't match, go back to that page to figure out what you mis
## Keep the app transpiling and running
## 让应用代码保持转译和运行
Enter the following command in the terminal window:
在控制台中敲下列命令:
<code-example language="sh" class="code-shell">
npm start
@ -100,16 +115,31 @@ Enter the following command in the terminal window:
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
这个命令会在“监听”模式下运行TypeScript编译器当代码变化时它会自动重新编译。
同时,该命令还会在浏览器中启动该应用,并且当代码变化时刷新浏览器。
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
在后续构建《英雄指南》过程中,应用能持续运行,而不用中断服务来编译或刷新浏览器。
## Displaying heroes
## 显示我们的英雄
To display a list of heroes, you'll add heroes to the view's template.
要显示英雄列表,我们就要先往视图模板中添加一些英雄。
### Create heroes
### 创建英雄
Create an array of ten heroes.
我们先创建一个由十位英雄组成的数组。
<code-example path="toh-2/src/app/app.component.ts" region="hero-array" title="src/app/app.component.ts (hero array)">
@ -121,9 +151,16 @@ The `HEROES` array is of type `Hero`, the class defined in the previous page.
Eventually this app will fetch the list of heroes from a web service, but for now
you can display mock heroes.
`HEROES`是一个由`Hero`类的实例构成的数组,我们在第一部分定义过它。
我们当然希望从一个 Web 服务中获取这个英雄列表,但别急,我们得把步子迈得小一点,先用一组模拟出来的英雄。
### Expose heroes
### 暴露英雄
Create a public property in `AppComponent` that exposes the heroes for binding.
我们在`AppComponent`上创建一个公共属性,用来暴露这些英雄,以供绑定。
<code-example path="toh-2/src/app/app.component.1.html" region="hero-array-1" title="app.component.ts (hero array property)">
@ -134,6 +171,8 @@ Create a public property in `AppComponent` that exposes the heroes for binding.
The `heroes` type isn't defined because TypeScript infers it from the `HEROES` array.
我们并不需要明确定义`heroes`属性的数据类型TypeScript 能从`HEROES`数组中推断出来。
<div class="l-sub-section">
@ -142,15 +181,22 @@ The `heroes` type isn't defined because TypeScript infers it from the `HEROES` a
The hero data is separated from the class implementation
because ultimately the hero names will come from a data service.
英雄的数据从实现类中分离了出来,因为最终,英雄的名字会来自一个数据服务。
</div>
### Display hero names in a template
### 在模板中显示英雄
To display the hero names in an unordered list,
insert the following chunk of HTML below the title and above the hero details.
我们还要在模板中创建一个无序列表来显示这些英雄的名字。
那就在标题和英雄详情之间,插入下面这段 HTML 代码。
<code-example path="toh-2/src/app/app.component.1.html" region="heroes-template-1" title="app.component.ts (heroes template)" linenums="false">
@ -161,13 +207,21 @@ insert the following chunk of HTML below the title and above the hero details.
Now you can fill the template with hero names.
现在,我们有了一个模板。接下来,就用英雄们的数据来填充它。
### List heroes with ngFor
### 通过 ngFor 来显示英雄列表
The goal is to bind the array of heroes in the component to the template, iterate over them,
and display them individually.
我们想要把组件中的`heroes`数组绑定到模板中,迭代并逐个显示它们。
Modify the `<li>` tag by adding the built-in directive `*ngFor`.
首先,修改`<li>`标签,往上添加内置指令`*ngFor`。
<code-example path="toh-2/src/app/app.component.1.html" region="heroes-ngfor-1" title="app.component.ts (ngFor)">
@ -183,19 +237,28 @@ The (`*`) prefix to `ngFor` is a critical part of this syntax.
It indicates that the `<li>` element and its children
constitute a master template.
`ngFor`的`*`前缀表示`<li>`及其子元素组成了一个主控模板。
The `ngFor` directive iterates over the component's `heroes` array
and renders an instance of this template for each hero in that array.
`ngFor`指令在`AppComponent.heroes`属性返回的`heroes`数组上迭代,并输出此模板的实例。
The `let hero` part of the expression identifies `hero` as the template input variable,
which holds the current hero item for each iteration.
You can reference this variable within the template to access the current hero's properties.
引号中赋值给`ngFor`的那段文本表示“*从`heroes`数组中取出每个英雄,存入一个局部的`hero`变量,并让它在相应的模板实例中可用*”。
Read more about `ngFor` and template input variables in the
[Showing an array property with *ngFor](guide/displaying-data#ngFor) section of the
[Displaying Data](guide/displaying-data) page and the
[ngFor](guide/template-syntax#ngFor) section of the
[Template Syntax](guide/template-syntax) page.
要学习更多关于`ngFor`和模板输入变量的知识,参见[显示数据](guide/displaying-data)一章的[用*ngFor显示数组属性](guide/displaying-data#ngFor)和
[模板语法](guide/template-syntax)章的[ngFor](guide/template-syntax#ngFor)。
</div>
@ -204,6 +267,7 @@ Read more about `ngFor` and template input variables in the
Within the `<li>` tags, add content
that uses the `hero` template variable to display the hero's properties.
接着,我们在`<li>`标签中插入一些内容,以便使用模板变量`hero`来显示英雄的属性。
<code-example path="toh-2/src/app/app.component.1.html" region="ng-for" title="app.component.ts (ngFor template)" linenums="false">
@ -214,12 +278,21 @@ that uses the `hero` template variable to display the hero's properties.
When the browser refreshes, a list of heroes appears.
当浏览器刷新时,我们就看到了英雄列表。
### Style the heroes
### 给我们的英雄们“美容”
Users should get a visual cue of which hero they are hovering over and which hero is selected.
当用户的鼠标划过英雄或选中一个英雄时,我们得让他/她看起来醒目一点。
To add styles to your component, set the `styles` property on the `@Component` decorator
to the following CSS classes:
要想给我们的组件添加一些样式,请把`@Component`装饰器的`styles`属性设置为下列 CSS 类:
<code-example path="toh-2/src/app/app.component.ts" region="styles" title="src/app/app.component.ts (styles)" linenums="false">
@ -229,13 +302,21 @@ to the following CSS classes:
Remember to use the backtick notation for multi-line strings.
注意,我们又使用了反引号语法来书写多行字符串。
Adding these styles makes the file much longer. In a later page you'll move the styles to a separate file.
添加这些样式会让此文件变得更长。在后面的章节中,我们将会把这些样式移到单独的文件中去。
When you assign styles to a component, they are scoped to that specific component.
These styles apply only to the `AppComponent` and don't affect the outer HTML.
当我们为一个组件指定样式时,它们的作用域将仅限于该组件。
上面的例子中,这些样式只会作用于`AppComponent`组件,而不会“泄露”到外部 HTML 中。
The template for displaying heroes should look like this:
用于显示英雄们的模板应该是这样的:
<code-example path="toh-2/src/app/app.component.1.html" region="heroes-styled" title="src/app/app.component.ts (styled heroes)" linenums="false">
@ -246,18 +327,33 @@ The template for displaying heroes should look like this:
## Selecting a hero
## 选择英雄
The app now displays a list of heroes as well as a single hero in the details view. But
the list and the details view are not connected.
When users select a hero from the list, the selected hero should appear in the details view.
This UI pattern is known as "master/detail."
In this case, the _master_ is the heroes list and the _detail_ is the selected hero.
我们的应用已经有了英雄列表和单个英雄的详情视图。
但列表和单独的英雄之间还没有任何关联。
我们希望用户在列表中选中一个英雄,然后让这个被选中的英雄出现在详情视图中。
这种 UI 布局模式,通常被称为“主从结构”。
在这个例子中,主视图是英雄列表,从视图则是被选中的英雄。
Next you'll connect the master to the detail through a `selectedHero` component property,
which is bound to a click event.
接下来,我们要通过组件中的一个`selectedHero`属性来连接主从视图,它被绑定到了点击事件上。
### Handle click events
### 处理点击事件
Add a click event binding to the `<li>` like this:
我们再往`<li>`元素上插入一句点击事件的绑定代码:
<code-example path="toh-2/src/app/app.component.1.html" region="selectedHero-click" title="app.component.ts (template excerpt)" linenums="false">
@ -271,6 +367,11 @@ The `onSelect(hero)` expression calls the `AppComponent` method, `onSelect()`,
passing the template input variable `hero`, as an argument.
That's the same `hero` variable you defined previously in the `ngFor` directive.
圆括号标识`<li>`元素上的`click`事件是绑定的目标。
等号右边的`onSelect(hero)`表达式调用`AppComponent`的`onSelect()`方法,并把模板输入变量`hero`作为参数传进去。
它是我们前面在`ngFor`指令中定义的那个`hero`变量。
<div class="l-sub-section">
@ -280,16 +381,26 @@ Learn more about event binding at the
[Event binding](guide/template-syntax#event-binding) section of the
[Template Syntax](guide/template-syntax) page.
关于事件绑定的更多内容,参见:
[用户输入](guide/user-input)页 和
[模板语法](guide/template-syntax)页的[事件绑定](guide/template-syntax#event-binding)节。
</div>
### Add a click handler to expose the selected hero
### 添加点击处理器以暴露选中的英雄
You no longer need the `hero` property because you're no longer displaying a single hero; you're displaying a list of heroes.
But the user will be able to select one of the heroes by clicking on it.
So replace the `hero` property with this simple `selectedHero` property:
我们不再需要`AppComponent`的`hero`属性,因为不需要再显示单个的英雄,我们只需要显示英雄列表。但是用户可以点选一个英雄。
所以我们要把`hero`属性**替换**成`selectedHero`属性。
<code-example path="toh-2/src/app/app.component.ts" region="selected-hero" title="src/app/app.component.ts (selectedHero)">
@ -300,8 +411,13 @@ So replace the `hero` property with this simple `selectedHero` property:
The hero names should all be unselected before the user picks a hero, so
you won't initialize the `selectedHero` as you did with `hero`.
在用户选取一个英雄之前,所有的英雄名字都应该是未选中的。所以我们不希望像`hero`一样初始化`selectedHero`变量。
Add an `onSelect()` method that sets the `selectedHero` property to the `hero` that the user clicks.
现在,**添加一个`onSelect`方法**,用于将用户点击的英雄赋给`selectedHero`属性。
<code-example path="toh-2/src/app/app.component.ts" region="on-select" title="src/app/app.component.ts (onSelect)" linenums="false">
</code-example>
@ -311,6 +427,8 @@ Add an `onSelect()` method that sets the `selectedHero` property to the `hero` t
The template still refers to the old `hero` property.
Bind to the new `selectedHero` property instead as follows:
我们将把所选英雄的详细信息显示在模板中。目前,它仍然引用之前的`hero`属性。
我们这就修改模板,让它绑定到新的`selectedHero`属性。
<code-example path="toh-2/src/app/app.component.1.html" region="selectedHero-details" title="app.component.ts (template excerpt)" linenums="false">
@ -321,11 +439,17 @@ Bind to the new `selectedHero` property instead as follows:
### Hide the empty detail with ngIf
### 使用 ngIf 隐藏空的详情
When the app loads, `selectedHero` is undefined.
The selected hero is initialized when the user clicks a hero's name.
Angular can't display properties of the undefined `selectedHero` and throws the following error,
visible in the browser's console:
当应用加载时,我们会看到一个英雄列表,但还没有任何英雄被选中。
`selectedHero`属性是`undefined`。
因此,我们会看到浏览器控制台中出现下列错误:
<code-example format="nocode">
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
@ -337,9 +461,14 @@ visible in the browser's console:
Although `selectedHero.name` is displayed in the template,
you must keep the hero detail out of the DOM until there is a selected hero.
虽然我们要在模板中显示的是`selectedHero.name`但在选中了一个英雄之前我们必须让这些英雄详情留在DOM之外。
Wrap the HTML hero detail content of the template with a `<div>`.
Then add the `ngIf` built-in directive and set it to the `selectedHero` property of the component.
我们可以把模板中的英雄详情内容区放在一个`<div>`中。
然后,添加一个`ngIf`内置指令,把`ngIf`的值设置为组件的`selectedHero`属性。
<code-example path="toh-2/src/app/app.component.1.html" region="ng-if" title="src/app/app.component.ts (ngIf)" linenums="false">
@ -353,6 +482,8 @@ Then add the `ngIf` built-in directive and set it to the `selectedHero` property
Don't forget the asterisk (`*`) in front of `ngIf`.
别忘了`ngIf`前的星号 (`*`)。
</div>
@ -360,13 +491,20 @@ Don't forget the asterisk (`*`) in front of `ngIf`.
The app no longer fails and the list of names displays again in the browser.
应用不再出错,而名字列表也再次显示在浏览器中。
When there is no selected hero, the `ngIf` directive removes the hero detail HTML from the DOM.
There are no hero detail elements or bindings to worry about.
当没有选中英雄时,`ngIf`指令会从 DOM 中移除表示英雄详情的这段 HTML 。
没有了表示英雄详情的元素,也就不用担心绑定问题。
When the user picks a hero, `selectedHero` becomes defined and
`ngIf` puts the hero detail content into the DOM and evaluates the nested bindings.
当用户选取了一个英雄,`selectedHero`变成了“已定义的”值,于是`ngIf`把英雄详情加回 DOM 中,并计算它所嵌套的各种绑定。
<div class="l-sub-section">
@ -378,6 +516,10 @@ Read more about `ngIf` and `ngFor` in the
[Template Syntax](guide/template-syntax) page.
要了解更多`ngIf``ngFor`和其它结构型指令的信息,参见
[结构型指令](guide/structural-directives)和
[模板语法](guide/template-syntax)章的[内置指令](guide/template-syntax#directives)部分。
</div>
@ -385,22 +527,33 @@ Read more about `ngIf` and `ngFor` in the
### Style the selected hero
### 给所选英雄添加样式
While the selected hero details appear below the list, it's difficult to identify the selected hero within the list itself.
我们在下面的详情区看到了选中的英雄,但是我们还是没法在上面的列表区快速定位这位英雄。
In the `styles` metadata that you added above, there is a custom CSS class named `selected`.
To make the selected hero more visible, you'll apply this `selected` class to the `<li>` when the user clicks on a hero name.
For example, when the user clicks "Magneta", it should render with a distinctive but subtle background color
like this:
在我们前面添加的`styles`元数据中,有一个名叫`selected`的自定义CSS类。
要想让选中的英雄更加醒目,当用户点击一个英雄名字时,我们要为`<li>`添加`selected`类。
例如当用户点击“Magneta”时它应该使用不一样的醒目的背景色。
<figure class='image-display'>
<img src='assets/images/devguide/toh/heroes-list-selected.png' alt="Selected hero"></img>
<img src='assets/images/devguide/toh/heroes-list-selected.png' alt="选中的英雄"></img>
</figure>
In the template, add the following `[class.selected]` binding to the `<li>`:
在这个模板中,往`<li>`上添加一个`[class.selected]`绑定:
<code-example path="toh-2/src/app/app.component.1.html" region="class-selected-1" title="app.component.ts (setting the CSS class)" linenums="false">
</code-example>
@ -410,6 +563,8 @@ In the template, add the following `[class.selected]` binding to the `<li>`:
When the expression (`hero === selectedHero`) is `true`, Angular adds the `selected` CSS class.
When the expression is `false`, Angular removes the `selected` class.
当表达式(`hero === selectedHero`)为`true`时Angular会添加一个CSS类`selected`。为`false`时则会移除`selected`类。
<div class="l-sub-section">
@ -418,6 +573,8 @@ When the expression is `false`, Angular removes the `selected` class.
Read more about the `[class]` binding in the [Template Syntax](guide/template-syntax#ngClass "Template syntax: NgClass") guide.
关于`[class]`绑定的更多信息,参见[模板语法](guide/template-syntax#ngClass "Template syntax: NgClass")。
</div>
@ -425,6 +582,7 @@ Read more about the `[class]` binding in the [Template Syntax](guide/template-sy
The final version of the `<li>` looks like this:
<code-example path="toh-2/src/app/app.component.1.html" region="class-selected-2" title="app.component.ts (styling each hero)" linenums="false">
</code-example>
@ -433,15 +591,20 @@ The final version of the `<li>` looks like this:
After clicking "Magneta", the list should look like this:
浏览器重新加载了我们的应用。
我们选中英雄 Magneta通过背景色的变化它被清晰的标记出来。
<figure class='image-display'>
<img src='assets/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app"></img>
<img src='assets/images/devguide/toh/heroes-list-1.png' alt="英雄列表应用的输出"></img>
</figure>
Here's the complete `app.component.ts` as of now:
完整的`app.component.ts`文件如下:
<code-example path="toh-2/src/app/app.component.ts" title="src/app/app.component.ts">
@ -452,15 +615,38 @@ Here's the complete `app.component.ts` as of now:
## The road you've travelled
## 已走的路
Here's what you achieved in this page:
在本章中,我们完成了以下内容:
* The Tour of Heroes app displays a list of selectable heroes.
我们的《英雄指南》现在显示一个可选英雄的列表
* You added the ability to select a hero and show the hero's details.
我们可以选择英雄,并显示这个英雄的详情
* You learned how to use the built-in directives `ngIf` and `ngFor` in a component's template.
Your app should look like this <live-example></live-example>.
我们学会了如何在组件模板中使用内置的`ngIf`和`ngFor`指令
## The road ahead
You've expanded the Tour of Heroes app, but it's far from complete.
An app shouldn't be one monolithic component.
In the [next page](tutorial/toh-pt3), you'll split the app into subcomponents and make them work together.
Run the <live-example></live-example> for this part.
运行这部分的<live-example></live-example>
### The Road Ahead
### 前方的路
Our Tour of Heroes has grown, but its far from complete.
We can't put the entire app into a single component.
We need to break it up into sub-components and teach them to work together
as we learn in the [next chapter](tutorial/toh-pt3).
我们的《英雄指南》长大了,但还远远不够完善。
我们显然不能把整个应用都放进一个组件中。
我们需要把它拆分成一系列子组件,然后教它们协同工作,
就像我们将在[下一章](tutorial/toh-pt3)学到的那样。

View File

@ -1,8 +1,8 @@
@title
Multiple Components
多个组件
@intro
Refactor the master/detail view into separate components.
把主从结构的页面重构成多个组件
@description
@ -13,18 +13,34 @@ Then it became a master/detail form with both a list of heroes and the hero deta
Soon there will be new requirements and capabilities.
You can't keep piling features on top of features in one component; that's not maintainable.
此刻,`AppComponent`负责*所有事*。
起初,它只显示单个英雄的详情。然后,它变成了主从结构,同时显示英雄列表和一个英雄详情。
现在,我们很快又会有新需求了。
我们不能把这些需求全都放在一个组件中,否则将不可维护。
You'll need to break it up into sub-components, each focused on a specific task or workflow.
Eventually, the `AppComponent` could become a simple shell that hosts those sub-components.
我们要把它拆分成一些子组件,每个子组件只聚焦在一个特定的任务或工作流上。
最后,`AppComponent`将会变成一个简单的壳,用来作为那些子组件的宿主。
In this page, you'll take the first step in that direction by carving out the hero details into a separate, reusable component.
When you're done, the app should look like this <live-example></live-example>.
本章中,我们要做的第一步就是把英雄详情拆分到一个独立的、可复用的组件中。
做完这些,应用是这样的:<live-example></live-example>
## Where you left off
## 延续上一步教程
Before getting started on this page, verify that you have the following structure from earlier in the Tour of Heroes.
If not, go back to the previous pages.
在继续《英雄指南》之前,先检查一下,是否已经有了如下目录结构。如果没有,回上一章,看看错过了哪里。
<div class='filetree'>
@ -96,22 +112,39 @@ Keep the app transpiling and running while you build the Tour of Heroes
by entering the `npm start` command in a terminal window
[as you did before](tutorial/toh-pt1#keep-transpiling "Keep the app running").
[像以前一样](tutorial/toh-pt1#keep-transpiling "Keep the app running"),在终端窗口中输入`npm start`命令,以便在构建《英雄指南》时保持持续转译和运行。
## Make a hero detail component
## 制作英雄详情组件
Add a file named `hero-detail.component.ts` to the `app/` folder.
This file will hold the new `HeroDetailComponent`.
往`app/`文件夹下添加一个名叫`hero-detail.component.ts`的文件。这个文件中会存放这个新的`HeroDetailComponent`。
The file and component names follow the standard described in the Angular
[style guide](guide/style-guide#naming).
文件名和组件名遵循[风格指南](guide/style-guide#naming)中的标准方式。
* The component _class_ name should be written in _upper camel case_ and end in the word "Component".
The hero detail component class is `HeroDetailComponent`.
组件的类名应该是*大驼峰形式*,并且以`Component`结尾。
因此英雄详情组件的类名是`HeroDetailComponent`。
* The component _file_ name should be spelled in [_lower dash case_](guide/glossary#dash-case),
each word separated by dashes, and end in `.component.ts`.
The `HeroDetailComponent` class goes in the `hero-detail.component.ts` file.
组件的文件名应该是[小写中线形式](guide/glossary#dash-case),每个单词之间用中线分隔,并且以`.component.ts`结尾。
因此`HeroDetailComponent`类应该放在`hero-detail.component.ts`文件中。
Start writing the `HeroDetailComponent` as follows:
`HeroDetailComponent`的代码如下:
<code-example path="toh-3/app/hero-detail.component.1.ts" region="v1" title="app/hero-detail.component.ts (initial version)" linenums="false">
@ -124,23 +157,42 @@ Start writing the `HeroDetailComponent` as follows:
To define a component, you always import the `Component` symbol.
要定义一个组件,我们总是要先导入符号`Component`。
The `@Component` decorator provides the Angular metadata for the component.
The CSS selector name, `hero-detail`, will match the element tag
that identifies this component within a parent component's template.
[Near the end of this tutorial page](tutorial/toh-pt3#add-hero-detail "Add the HeroDetailComponent to the AppComponent"),
you'll add a `<hero-detail>` element to the `AppComponent` template.
`@Component`装饰器为组件提供了Angular元数据。
CSS选择器的名字`hero-detail`会匹配元素的标签名,用于在父组件的模板中标记出当前组件的位置。
[本章的最后](tutorial/toh-pt3#add-hero-detail "Add the HeroDetailComponent to the AppComponent"),我们会把`<hero-detail>`添加到`AppComponent`的模板中。
Always `export` the component class because you'll always `import` it elsewhere.
总是`export`这个组件类,因为你必然会在别处`import`它。
### Hero detail template
### 英雄详情的模板
To move the hero detail view to the `HeroDetailComponent`,
cut the hero detail _content_ from the bottom of the `AppComponent` template
and paste it into a new `template` property in the `@Component` metadata.
要把英雄详情的视图移入`HeroDetailComponent`,只要把英雄详情的 *内容* 从`AppComponent`模板的底部剪切出来,
粘贴到`@Component`元数据的`template`属性中就可以了。
The `HeroDetailComponent` has a _hero_, not a _selected hero_.
Replace the word, "selectedHero", with the word, "hero", everywhere in the template.
When you're done, the new template should look like this:
`HeroDetailComponent`有一个 `hero`属性,而不再是`selectedHero`。
所以我们也要在模板中把所有的`selectedHero`替换为`hero`。
这些完成之后,新的模板是这样的:
<code-example path="toh-3/src/app/hero-detail.component.ts" region="template" title="src/app/hero-detail.component.ts (template)" linenums="false">
@ -150,9 +202,15 @@ When you're done, the new template should look like this:
### Add the *hero* property
### 添加`hero`属性
The `HeroDetailComponent` template binds to the component's `hero` property.
Add that property to the `HeroDetailComponent` class like this:
`HeroDetailComponent`模板绑定到了该组件的`hero`属性上。
把这个属性添加到`HeroDetailComponent`类上,就像这样:
<code-example path="toh-3/app/hero-detail.component.1.ts" region="hero" title="src/app/hero-detail.component.ts (hero property)">
</code-example>
@ -164,8 +222,15 @@ The `Hero` class is still in the `app.component.ts` file.
Now there are two components that need to reference the `Hero` class.
The Angular [style guide](guide/style-guide#rule-of-one "Style guide: rule of one") recommends one class per file anyway.
`hero`属性的类型是`Hero`。
`Hero`类仍然在`app.component.ts`文件中。
现在,有两个组件需要`Hero`类的引用。
而Angular[风格指南](guide/style-guide#rule-of-one "Style guide: rule of one")建议每个文件中只有一个类。
Move the `Hero` class from `app.component.ts` to its own `hero.ts` file.
因此我们要把`Hero`类从`app.component.ts`移到它自己的`hero.ts`文件中:
<code-example path="toh-3/src/app/hero.ts" title="src/app/hero.ts" linenums="false">
@ -176,6 +241,10 @@ Move the `Hero` class from `app.component.ts` to its own `hero.ts` file.
Now that the `Hero` class is in its own file, the `AppComponent` and the `HeroDetailComponent` have to import it.
Add the following `import` statement near the top of _both_ the `app.component.ts` and the `hero-detail.component.ts` files.
现在,`Hero`类有了自己的文件,`AppComponent` 和 `HeroDetailComponent` 就要`import`它了。
把下列`import`语句添加到`app.component.ts`和`hero-detail.component.ts`文件的顶部。
<code-example path="toh-3/app/hero-detail.component.1.ts" region="hero-import" title="toh-3/app/hero-detail.component.ts">
</code-example>
@ -184,11 +253,19 @@ Add the following `import` statement near the top of _both_ the `app.component.t
### The *hero* property is an *input* property
### *hero*属性是一个***输入***属性
[Later in this page](tutorial/toh-pt3#add-hero-detail "Add the HeroDetailComponent to the AppComponent"),
the parent `AppComponent` will tell the child `HeroDetailComponent` which hero to display
by binding its `selectedHero` to the `hero` property of the `HeroDetailComponent`.
The binding will look like this:
[在本章稍后的部分](tutorial/toh-pt3#add-hero-detail "Add the HeroDetailComponent to the AppComponent")
父组件`AppComponent`会告诉子组件`HeroDetailComponent`要显示哪个英雄,
告诉的方法是把它的`selectedHero`属性绑定到`HeroDetailComponent`的`hero`属性上。
这种绑定是这样的:
<code-example path="toh-3/app/app.component.1.html" region="hero-detail-binding" title="toh-3/app/app.component.html" linenums="false">
</code-example>
@ -200,8 +277,14 @@ makes it the *target* of a property binding expression.
You must declare a *target* binding property to be an *input* property.
Otherwise, Angular rejects the binding and throws an error.
在等号的左边,是方括号围绕的`hero`属性,这表示它是属性绑定表达式的*目标*。
我们要绑定到的*目标*属性必须是一个*输入*属性否则Angular会拒绝绑定并抛出一个错误。
First, amend the `@angular/core` import statement to include the `Input` symbol.
首先,修改`@angular/core`导入语句,使其包含符号`Input`。
<code-example path="toh-3/src/app/hero-detail.component.ts" region="import-input" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
</code-example>
@ -211,6 +294,9 @@ First, amend the `@angular/core` import statement to include the `Input` symbol.
Then declare that `hero` is an *input* property by
preceding it with the `@Input` decorator that you imported earlier.
然后,通过在`hero`属性前面加上`@Input`装饰器,来表明它是一个输入属性。
<code-example path="toh-3/src/app/hero-detail.component.ts" region="hero" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
</code-example>
@ -224,6 +310,8 @@ preceding it with the `@Input` decorator that you imported earlier.
Read more about _input_ properties in the
[Attribute Directives](guide/attribute-directives#why-input) page.
要了解*输入属性*的更多知识,参见[属性型指令](guide/attribute-directives#why-input)页。
</div>
@ -231,6 +319,9 @@ Read more about _input_ properties in the
That's it. The `hero` property is the only thing in the `HeroDetailComponent` class.
现在,`hero`属性是`HeroDetailComponent`类中唯一的东西。
<code-example path="toh-3/src/app/hero-detail.component.ts" region="class" title="toh-3/src/app/hero-detail.component.ts" linenums="false">
</code-example>
@ -239,8 +330,13 @@ That's it. The `hero` property is the only thing in the `HeroDetailComponent` cl
All it does is receive a hero object through its `hero` input property and then bind to that property with its template.
它所做的一切就是通过它的输入属性`hero`接收一个英雄对象,然后把这个属性绑定到自己的模板中。
Here's the complete `HeroDetailComponent`.
下面是完整的`HeroDetailComponent`
<code-example path="toh-3/src/app/hero-detail.component.ts" title="src/app/hero-detail.component.ts">
</code-example>
@ -249,10 +345,18 @@ Here's the complete `HeroDetailComponent`.
## Declare _HeroDetailComponent_ in the _AppModule_
## 在`AppModule`中声明`HeroDetailComponent`
Every component must be declared in one&mdash;and only one&mdash;Angular module.
每个组件都必须在一个且只有一个Angular模块中声明。
Open `app.module.ts` in your editor and import the `HeroDetailComponent` so you can refer to it.
打开`app.module.ts`并且导入`HeroDetailComponent`,以便我们可以引用它。
<code-example path="toh-3/src/app/app.module.ts" region="hero-detail-import" title="src/app/app.module.ts">
</code-example>
@ -261,6 +365,8 @@ Open `app.module.ts` in your editor and import the `HeroDetailComponent` so you
Add `HeroDetailComponent` to the module's `declarations` array.
把`HeroDetailComponent`添加到该模块的`declarations`数组中。
<code-example path="toh-3/src/app/app.module.ts" region="declarations" title="src/app/app.module.ts" linenums="false">
@ -272,12 +378,19 @@ In general, the `declarations` array contains a list of application components,
A component must be declared in a module before other components can reference it.
This module declares only the two application components, `AppComponent` and `HeroDetailComponent`.
通常,`declarations`数组包含应用中属于该模块的组件、管道和指令的列表。
组件在被其它组件引用之前必须先在一个模块中声明过。
这个模块只声明了两个组件:`AppComponent` 和 `HeroDetailComponent`
<div class="l-sub-section">
Read more about Angular modules in the [NgModules](guide/ngmodule "Angular Modules (NgModule)") guide.
要了解关于Angular模块的更多知识参见[Angular模块](guide/ngmodule "Angular Modules (NgModule)")页。
</div>
@ -289,22 +402,36 @@ Read more about Angular modules in the [NgModules](guide/ngmodule "Angular Modul
## Add the _HeroDetailComponent_ to the _AppComponent_
## 把`HeroDetailComponent`添加到`AppComponent`中
The `AppComponent` is still a master/detail view.
It used to display the hero details on its own, before you cut out that portion of the template.
Now it will delegate to the `HeroDetailComponent`.
`AppComponent`仍然是主从视图。
在我们剪切模板之前,它自己显示英雄的详情。
现在,它委托给了`HeroDetailComponent`。
Recall that `hero-detail` is the CSS [`selector`](tutorial/toh-pt3#selector "HeroDetailComponent selector")
in the `HeroDetailComponent` metadata.
That's the tag name of the element that represents the `HeroDetailComponent`.
回想一下,`hero-detail`正是`HeroDetailComponent`元数据中使用的 CSS [`selector`](tutorial/toh-pt3#selector "HeroDetailComponent selector")
它是一个HTML元素的标签名用于表示`HeroDetailComponent`。
Add a `<hero-detail>` element near the bottom of the `AppComponent` template,
where the hero detail view used to be.
把`<hero-detail>`元素添加到`AppComponent`模板的底部,那里就是英雄详情视图所在的位置。
Coordinate the master `AppComponent` with the `HeroDetailComponent`
by binding the `selectedHero` property of the `AppComponent`
to the `hero` property of the `HeroDetailComponent`.
协调主视图`AppComponent`与`HeroDetailComponent`的方式是把`AppComponent`的`selectedHero`属性绑定到`HeroDetailComponent`的`hero`属性上。
<code-example path="toh-3/app/app.component.1.html" region="hero-detail-binding" title="app.component.ts (excerpt)" linenums="false">
</code-example>
@ -313,7 +440,11 @@ to the `hero` property of the `HeroDetailComponent`.
Now every time the `selectedHero` changes, the `HeroDetailComponent` gets a new hero to display.
每当`selectedHero`变化时,`HeroDetailComponent`就会显示一个新的英雄。
The revised `AppComponent` template should look like this:
修改后的`AppComponent`模板是这样的:
<code-example path="toh-3/src/app/app.component.ts" region="hero-detail-template" title="app.component.ts (excerpt)" linenums="false">
@ -324,24 +455,45 @@ The revised `AppComponent` template should look like this:
## What changed?
## 有哪些变化?
As [before](tutorial/toh-pt2), whenever a user clicks on a hero name,
the hero detail appears below the hero list.
But now the `HeroDetailView` is presenting those details.
仍然像[以前](tutorial/toh-pt2)一样,一旦用户点击了英雄的名字,英雄详情就会显示在英雄列表的下方。
不过现在改用`HeroDetailView`来表示英雄详情了。
Refactoring the original `AppComponent` into two components yields benefits, both now and in the future:
我们把原来的`AppComponent`重构成了两个组件具有一些显著优点,无论是现在还是未来:
1. You simplified the `AppComponent` by reducing its responsibilities.
通过缩减`AppComponent`的职责,我们简化了它。
1. You can evolve the `HeroDetailComponent` into a rich hero editor
without touching the parent `AppComponent`.
我们将来可以把`HeroDetailComponent`改进为功能更丰富的英雄编辑器,而不用动`AppComponent`。
1. You can evolve the `AppComponent` without touching the hero detail view.
同样,我们也可以改进`AppComponent`而不用动英雄详情视图。
1. You can re-use the `HeroDetailComponent` in the template of some future parent component.
我们可以在未来的其它父组件的模板中复用`HeroDetailComponent`。
### Review the app structure
### 审视本应用的代码结构
Verify that you have the following structure:
验证它是否已经有了如下结构:
<div class='filetree'>
@ -419,6 +571,8 @@ Verify that you have the following structure:
Here are the code files discussed in this page.
下面是我们在本章讨论的代码文件:
<code-tabs>
@ -445,23 +599,49 @@ Here are the code files discussed in this page.
## The road youve travelled
## 走过的路
Here's what you achieved in this page:
来盘点一下我们已经构建了什么。
* You created a reusable component.
我们创建了一个可复用组件
* You learned how to make a component accept input.
我们学会了如何让一个组件接收输入
* You learned to declare the required application directives in an Angular module. You
listed the directives in the `NgModule` decorator's `declarations` array.
我们学会了在 Angular 模块中声明该应用所需的指令。
只要把这些指令列在`NgModule`装饰器的`declarations`数组中就可以了。
* You learned to bind a parent component to a child component.
我们学会了把父组件绑定到子组件。
Your app should look like this <live-example></live-example>.
现在,应用应该变成了这样:<live-example></live-example>
## The road ahead
## 前方的路
The Tour of Heroes app is more reusable with shared components,
but its (mock) data is still hard coded within the `AppComponent`.
That's not sustainable.
Data access should be refactored to a separate service
and shared among the components that need data.
Youll learn to create services in the [next tutorial](tutorial/toh-pt4) page.
通过抽取共享组件,我们的《英雄指南》变得更有复用性了,但在`AppComponent`中,我们仍然使用着硬编码的模拟数据。显然,这种方式不能“可持续发展”。
我们要把数据访问逻辑重构到一个独立的服务中,并在需要数据的组件之间共享。
Youll learn to create services in the [next tutorial](tutorial/toh-pt4) page.
在[下一步](tutorial/toh-pt4),我们将学习如何创建服务。

View File

@ -1,31 +1,45 @@
@title
Services
服务
@intro
Create a reusable service to manage the hero data calls.
创建一个可复用的服务来调用英雄的数据
@description
As the Tour of Heroes app evolves, you'll add more components that need access to hero data.
随着《英雄指南》的成长,我们要添加更多需要访问英雄数据的组件。
Instead of copying and pasting the same code over and over,
you'll create a single reusable data service and
inject it into the components that need it.
Using a separate service keeps components lean and focused on supporting the view,
and makes it easy to unit-test components with a mock service.
为了不再把相同的代码复制一遍又一遍,我们要创建一个单一的可复用的数据服务,并且把它注入到需要它的那些组件中。
使用单独的服务可以保持组件精简使其集中精力为视图提供支持并且借助模拟Mock服务可以更容易的对组件进行单元测试。
Because data services are invariably asynchronous,
you'll finish the page with a *Promise*-based version of the data service.
由于数据服务总是异步的因此我们最终会提供一个基于承诺Promise的数据服务。
When you're done with this page, the app should look like this <live-example></live-example>.
当我们完成本章的内容是,本应用会变成这样:<live-example></live-example>
## Where you left off
## 延续上一步教程
Before continuing with the Tour of Heroes, verify that you have the following structure.
If not, go back to the previous pages.
在继续《英雄指南》之前,先检查一下,是否已经有如下目录结构。如果没有,回上一章,看看错过了哪里。
<div class='filetree'>
@ -102,8 +116,13 @@ If not, go back to the previous pages.
## Keep the app transpiling and running
## 让应用代码保持转译和运行
Enter the following command in the terminal window:
在终端窗口中输入如下命令:
<code-example language="sh" class="code-shell">
npm start
@ -115,22 +134,45 @@ Enter the following command in the terminal window:
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
这个命令会在“监听”模式下运行TypeScript编译器当代码变化时它会自动重新编译。
同时,该命令还会在浏览器中启动该应用,并且当代码变化时刷新浏览器。
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
在后续构建《英雄指南》过程中,应用能持续运行,而不用中断服务来编译或刷新浏览器。
## Creating a hero service
## 创建英雄服务
The stakeholders want to show the heroes in various ways on different pages.
Users can already select a hero from a list.
Soon you'll add a dashboard with the top performing heroes and create a separate view for editing hero details.
All three views need hero data.
客户向我们描绘了本应用更大的目标:想要在不同的页面中用多种方式显示英雄。
现在我们已经能从列表中选择一个英雄了,但这还不够。
很快,我们将添加一个仪表盘来显示表现最好的英雄,并创建一个独立视图来编辑英雄的详情。
所有这些视图都需要英雄数据。
At the moment, the `AppComponent` defines mock heroes for display.
However, defining heroes is not the component's job,
and you can't easily share the list of heroes with other components and views.
In this page, you'll move the hero data acquisition business to a single service that provides the data and
share that service with all components that need the data.
目前,`AppComponent`显示的是模拟数据。
不过,定义这些英雄并非组件的任务,否则我们没法与其它组件和视图共享这些英雄列表数据。
在这一章,我们将把获取英雄数据的任务重构为一个单独的服务,它将提供英雄数据,并把服务在所有需要英雄数据的组件间共享。
### Create the HeroService
Create a file in the `app` folder called `hero.service.ts`.
### 创建 HeroService
Create a file in the `app` folder called `hero.service.ts`.
在`app`目录下创建一个名叫`hero.service.ts`的文件。
<div class="l-sub-section">
@ -140,12 +182,19 @@ The naming convention for service files is the service name in lowercase followe
For a multi-word service name, use lower [dash-case](guide/glossary#!).
For example, the filename for `SpecialSuperHeroService` is `special-super-hero.service.ts`.
我们遵循的文件命名约定是:服务名称的小写形式(基本名),加上`.service`后缀。
如果服务名称包含多个单词,我们就把基本名部分写成中线形式 ([dash-case](guide/glossary#dash-case))。
例如,`SpecialSuperHeroService`服务应该被定义在`special-super-hero.service.ts`文件中。
</div>
Name the class `HeroService` and export it for others to import.
我们把这个类命名为`HeroService`,并导出它,以供别人使用。
<code-example path="toh-4/src/app/hero.service.1.ts" region="empty-class" title="src/app/hero.service.ts (starting point)" linenums="false">
@ -154,14 +203,23 @@ Name the class `HeroService` and export it for others to import.
### Injectable services
### 可注入的服务
Notice that you imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator.
注意,我们导入了 Angular 的`Injectable`函数,并作为`@Injectable()`装饰器使用这个函数。
<div class="callout is-helpful">
Don't forget the parentheses. Omitting them leads to an error that's difficult to diagnose.
**不要忘了写圆括号!**如果忘了写,就会导致一个很难诊断的错误。
</div>
@ -169,14 +227,26 @@ Don't forget the parentheses. Omitting them leads to an error that's difficult t
The `@Injectable()` decorator tells TypeScript to emit metadata about the service.
The metadata specifies that Angular may need to inject other dependencies into this service.
当 TypeScript 看到`@Injectable()`装饰器时,就会记下本服务的元数据。
如果 Angular 需要往这个服务中注入其它依赖,就会使用这些元数据。
Although the `HeroService` doesn't have any dependencies at the moment,
applying the `@Injectable()` decorator from the start ensures
consistency and future-proofing.
虽然*此时*`HeroService`还没有任何依赖,但我们还是得加上这个装饰器。
作为一项最佳实践,无论是出于提高统一性还是减少变更的目的,
都应该从一开始就加上`@Injectable()`装饰器。
### Getting hero data
### 获取英雄数据
Add a `getHeroes()` method stub.
添加一个名叫`getHeros`的桩方法。
<code-example path="toh-4/src/app/hero.service.1.ts" region="getHeroes-stub" title="src/app/hero.service.ts (getHeroes stub)" linenums="false">
@ -190,10 +260,19 @@ Removing data access from the component means
you can change your mind about the implementation anytime,
without touching the components that need hero data.
`HeroService`可以从任何地方获取`Hero`数据 —— Web服务、本地存储或模拟数据源。
从组件中移除数据访问逻辑意味着你可以随时更改这些实现方式,而不影响需要这些英雄数据的组件。
### Move the mock hero data
### 移动模拟的英雄数据
Cut the `HEROES` array from `app.component.ts` and paste it to a new file in the `app` folder named `mock-heroes.ts`.
Additionally, copy the `import {Hero} ...` statement because the heroes array uses the `Hero` class.
从`app.component.ts`文件中剪切`HEROS`数组,把它粘贴到`app`目录下一个名叫`mock-heroes.ts`的文件中。
还要复制`import {Hero}...`语句,因为我们的英雄数组用到了`Hero`类。
<code-example path="toh-4/src/app/mock-heroes.ts" title="src/app/mock-heroes.ts">
@ -203,9 +282,14 @@ Additionally, copy the `import {Hero} ...` statement because the heroes array us
The `HEROES` constant is exported so it can be imported elsewhere, such as the `HeroService`.
我们导出了`HEROES`常量,以便可以在其它地方导入它 &mdash; 例如`HeroService`服务。
In `app.component.ts`, where you cut the `HEROES` array,
add an uninitialized `heroes` property:
在刚刚剪切出`HEROES`数组的`app.component.ts`文件中,添加一个尚未初始化的`heroes`属性:
<code-example path="toh-4/src/app/app.component.1.ts" region="heroes-prop" title="src/app/app.component.ts (heroes property)" linenums="false">
</code-example>
@ -213,9 +297,16 @@ add an uninitialized `heroes` property:
### Return mocked hero data
### 返回模拟的英雄数据
Back in the `HeroService`, import the mock `HEROES` and return it from the `getHeroes()` method.
The `HeroService` looks like this:
回到`HeroService`,我们导入`HEROES`常量,并在`getHeroes`方法中返回它。
我们的`HeroService`服务现在是这样的:
<code-example path="toh-4/src/app/hero.service.1.ts" region="full" title="src/app/hero.service.ts" linenums="false">
</code-example>
@ -223,10 +314,18 @@ The `HeroService` looks like this:
### Import the hero service
### 导入HeroService
You're ready to use the `HeroService` in other components, starting with `AppComponent`.
我们可以在多个组件中使用 HeroService 服务了,先从 AppComponent 开始。
Import the `HeroService` so that you can reference it in the code.
先导入`HeroService`,以便我们可以在代码中引用它。
<code-example path="toh-4/src/app/app.component.ts" linenums="false" title="toh-4/src/app/app.component.ts (hero-service-import)" region="hero-service-import">
</code-example>
@ -234,10 +333,18 @@ Import the `HeroService` so that you can reference it in the code.
### Don't use *new* with the *HeroService*
### 不要`new`出`HeroService`
How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
该如何在运行中获得一个具体的`HeroService`实例呢?
You could create a new instance of the `HeroService` with `new` like this:
你可能想用`new`来创建`HeroService`的实例,就像这样:
<code-example path="toh-4/src/app/app.component.1.ts" region="new-service" title="toh-4/src/app/app.component.ts" linenums="false">
</code-example>
@ -246,27 +353,54 @@ You could create a new instance of the `HeroService` with `new` like this:
However, this option isn't ideal for the following reasons:
但这不是个好主意,有很多理由,例如:
* The component has to know how to create a `HeroService`.
If you change the `HeroService` constructor,
you must find and update every place you created the service.
Patching code in multiple places is error prone and adds to the test burden.
我们的组件得弄清楚该如何创建`HeroService`。
如果有一天我们修改了`HeroService`的构造函数,我们不得不找出创建过此服务的每一处代码,并修改它。
围着补丁代码转圈很容易导致错误,还会增加测试负担。
* You create a service each time you use `new`.
What if the service caches heroes and shares that cache with others?
You couldn't do that.
我们每次使用`new`都会创建一个新的服务实例。
如果这个服务需要缓存英雄列表,并把这个缓存共享给别人呢?怎么办?
没办法,做不到。
* With the `AppComponent` locked into a specific implementation of the `HeroService`,
switching implementations for different scenarios, such as operating offline or using
different mocked versions for testing, would be difficult.
我们把`AppComponent`锁定到`HeroService`的一个特定实现。
我们很难在不同的场景中切换实现。
例如,能离线操作吗?能在测试时使用不同的模拟版本吗?这可不容易。
### Inject the *HeroService*
### 注入 *HeroService*
Instead of using the *new* line, you'll add two lines.
你可以用两行代码代替用`new`时的一行:
* Add a constructor that also defines a private property.
添加一个构造函数,并定义一个私有属性。
* Add to the component's `providers` metadata.
添加组件的`providers`元数据。
Add the constructor:
添加构造函数:
<code-example path="toh-4/src/app/app.component.1.ts" region="ctor" title="src/app/app.component.ts (constructor)">
</code-example>
@ -276,8 +410,13 @@ Add the constructor:
The constructor itself does nothing. The parameter simultaneously
defines a private `heroService` property and identifies it as a `HeroService` injection site.
构造函数自己什么也不用做,它在参数中定义了一个私有的`heroService`属性,并把它标记为注入`HeroService`的靶点。
Now Angular knows to supply an instance of the `HeroService` when it creates an `AppComponent`.
现在,当创建`AppComponent`实例时Angular 知道需要先提供一个`HeroService`的实例。
<div class="l-sub-section">
@ -285,6 +424,9 @@ Now Angular knows to supply an instance of the `HeroService` when it creates an
Read more about dependency injection in the [Dependency Injection](guide/dependency-injection) page.
更多依赖注入的信息,见[依赖注入](guide/dependency-injection)。
</div>
@ -292,8 +434,14 @@ Read more about dependency injection in the [Dependency Injection](guide/depende
The *injector* doesn't know yet how to create a `HeroService`.
If you ran the code now, Angular would fail with this error:
*注入器*还不知道该如何创建`HeroService`。
如果现在运行我们的代码Angular 就会失败,并报错:
<code-example format="nocode">
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
(异常:没有 HeroService 的提供商!(AppComponent -> HeroService))
</code-example>
@ -302,6 +450,8 @@ To teach the injector how to make a `HeroService`,
add the following `providers` array property to the bottom of the component metadata
in the `@Component` call.
我们还得注册一个`HeroService`**提供商**,来告诉*注入器*如何创建`HeroService`。
要做到这一点,我们在`@Component`组件的元数据底部添加`providers`数组属性如下:
<code-example path="toh-4/src/app/app.component.1.ts" linenums="false" title="toh-4/src/app/app.component.ts (providers)" region="providers">
@ -313,14 +463,26 @@ in the `@Component` call.
The `providers` array tells Angular to create a fresh instance of the `HeroService` when it creates an `AppComponent`.
The `AppComponent`, as well as its child components, can use that service to get hero data.
`providers`数组告诉 Angular当它创建新的`AppComponent`组件时,也要创建一个`HeroService`的新实例。
`AppComponent`会使用那个服务来获取英雄列表,在它组件树中的每一个子组件也同样如此。
{@a child-component}
### *getHeroes()* in the *AppComponent*
### *AppComponent* 中的 *getHeroes()*
The service is in a `heroService` private variable.
该服务被存入了一个私有变量`heroService`中。
You could call the service and get the data in one line.
我们可以在同一行内调用此服务,并获得数据。
<code-example path="toh-4/src/app/app.component.1.ts" region="get-heroes" title="toh-4/src/app/app.component.ts" linenums="false">
</code-example>
@ -329,6 +491,8 @@ You could call the service and get the data in one line.
You don't really need a dedicated method to wrap one line. Write it anyway:
在真实的世界中,我们并不需要把一行代码包装成一个专门的方法,但无论如何,我们在演示代码中先这么写:
<code-example path="toh-4/src/app/app.component.1.ts" linenums="false" title="toh-4/src/app/app.component.ts (getHeroes)" region="getHeroes">
@ -337,31 +501,50 @@ You don't really need a dedicated method to wrap one line. Write it anyway:
{@a oninit}
### The *ngOnInit* lifecycle hook
### *ngOnInit* 生命周期钩子
`AppComponent` should fetch and display hero data with no issues.
毫无疑问,`AppComponent`应该获取英雄数据并显示它。
You might be tempted to call the `getHeroes()` method in a constructor, but
a constructor should not contain complex logic,
especially a constructor that calls a server, such as as a data access method.
The constructor is for simple initializations, like wiring constructor parameters to properties.
你可能想在构造函数中调用`getHeroes()`方法,但构造函数不应该包含复杂的逻辑,特别是那些需要从服务器获取数据的逻辑更是如此。构造函数是为了简单的初始化工作而设计的,例如把构造函数的参数赋值给属性。
To have Angular call `getHeroes()`, you can implement the Angular *ngOnInit lifecycle hook*.
Angular offers interfaces for tapping into critical moments in the component lifecycle:
at creation, after each change, and at its eventual destruction.
只要我们实现了 Angular 的 **ngOnInit** *生命周期钩子*Angular 就会主动调用这个钩子。
Angular提供了一些接口用来介入组件生命周期的几个关键时间点刚创建时、每次变化时以及最终被销毁时。
Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
每个接口都有唯一的一个方法。只要组件实现了这个方法Angular 就会在合适的时机调用它。
<div class="l-sub-section">
Read more about lifecycle hooks in the [Lifecycle Hooks](guide/lifecycle-hooks) page.
更多生命周期钩子信息,见[生命周期钩子](guide/lifecycle-hooks)。
</div>
Here's the essential outline for the `OnInit` interface (don't copy this into your code):
这是`OnInit`接口的基本轮廓(但不要拷贝到你自己的代码中):
<code-example path="toh-4/src/app/app.component.1.ts" region="on-init" title="toh-4/src/app/app.component.ts" linenums="false">
</code-example>
@ -370,8 +553,12 @@ Here's the essential outline for the `OnInit` interface (don't copy this into yo
Add the implementation for the `OnInit` interface to your export statement:
往export语句中添加`OnInit`接口的实现:
<code-example format="nocode">
export class AppComponent implements OnInit {}
</code-example>
@ -379,6 +566,10 @@ Add the implementation for the `OnInit` interface to your export statement:
Write an `ngOnInit` method with the initialization logic inside. Angular will call it
at the right time. In this case, initialize by calling `getHeroes()`.
我们写了一个带有初始化逻辑的`ngOnInit`方法Angular会在适当的时候调用它。
在这个例子中,我们通过调用`getHeroes()`来完成初始化。
<code-example path="toh-4/src/app/app.component.1.ts" linenums="false" title="toh-4/src/app/app.component.ts (ng-on-init)" region="ng-on-init">
</code-example>
@ -387,12 +578,20 @@ at the right time. In this case, initialize by calling `getHeroes()`.
The app should run as expected, showing a list of heroes and a hero detail view
when you click on a hero name.
我们的应用将会像期望的那样运行,显示英雄列表,并且在我们点击英雄的名字时,显示英雄的详情。
{@a async}
## Async services and Promises
## 异步服务与承诺
The `HeroService` returns a list of mock heroes immediately;
its `getHeroes()` signature is synchronous.
我们的`HeroService`立即返回一个模拟的英雄列表,它的`getHeroes()`函数签名是同步的。
<code-example path="toh-4/src/app/app.component.1.ts" region="get-heroes" title="toh-4/src/app/app.component.ts" linenums="false">
</code-example>
@ -403,17 +602,28 @@ Eventually, the hero data will come from a remote server.
When using a remote server, users don't have to wait for the server to respond;
additionally, you aren't able to block the UI during the wait.
但最终,英雄的数据会从远端服务器获取。当使用远端服务器时,用户不会等待服务器的响应。换句话说,你没法在等待期间阻塞浏览器界面。
To coordinate the view with the response,
you can use *Promises*, which is an asynchronous
technique that changes the signature of the `getHeroes()` method.
为了协调视图与响应,我们可以使用*承诺Promise*,它是一种异步技术,它会改变`getHeroes()`方法的签名。
### The hero service makes a Promise
### `HeroService`会生成一个承诺
A *Promise* essentially promises to call back when the results are ready.
You ask an asynchronous service to do some work and give it a callback function.
The service does that work and eventually calls the function with the results or an error.
**承诺** 就是 …… 好吧,它就是一个承诺,在有了结果时,它承诺会回调我们。
我们请求一个异步服务去做点什么,并且给它一个回调函数。
它会去做(在某个地方),一旦完成,它就会调用我们的回调函数,并通过参数把工作结果或者错误信息传给我们。
<div class="l-sub-section">
@ -422,6 +632,8 @@ This is a simplified explanation. Read more about ES2015 Promises in the
[Promises for asynchronous programming](http://exploringjs.com/es6/ch_promises.html) page of
[Exploring ES6](http://http://exploringjs.com/es6.html).
这里只是粗略说说,要了解更多 ES2015 Promise 的信息,见[ES6概览](http://http://exploringjs.com/es6.html)中的[承诺与异步编程](http://exploringjs.com/es6/ch_promises.html)。
</div>
@ -429,6 +641,9 @@ This is a simplified explanation. Read more about ES2015 Promises in the
Update the `HeroService` with this Promise-returning `getHeroes()` method:
把`HeroService`的`getHeroes`方法改写为返回承诺的形式:
<code-example path="toh-4/src/app/hero.service.ts" region="get-heroes" title="src/app/hero.service.ts (excerpt)" linenums="false">
</code-example>
@ -438,10 +653,17 @@ Update the `HeroService` with this Promise-returning `getHeroes()` method:
You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server,
by returning an *immediately resolved Promise* with the mock heroes as the result.
我们继续使用模拟数据。我们通过返回一个 *立即解决的承诺* 的方式,模拟了一个超快、零延迟的超级服务器。
### Act on the Promise
### 基于承诺的行动
As a result of the change to `HeroService`, `this.heroes` is now set to a `Promise` rather than an array of heroes.
修改`HeroService`之后,`this.heroes`会被赋值为一个`Promise`而不再是英雄数组。
<code-example path="toh-4/src/app/app.component.1.ts" region="getHeroes" title="src/app/app.component.ts (getHeroes - old)" linenums="false">
</code-example>
@ -451,8 +673,14 @@ As a result of the change to `HeroService`, `this.heroes` is now set to a `Promi
You have to change the implementation to *act on the `Promise` when it resolves*.
When the `Promise` resolves successfully, you'll have heroes to display.
我们得修改这个实现,把它变成*基于承诺*的,并在承诺的事情被解决时再行动。
一旦承诺的事情被成功解决Resolve我们就会显示英雄数据。
Pass the callback function as an argument to the Promise's `then()` method:
我们把回调函数作为参数传给承诺对象的**then**方法:
<code-example path="toh-4/src/app/app.component.ts" region="get-heroes" title="src/app/app.component.ts (getHeroes - revised)" linenums="false">
</code-example>
@ -467,28 +695,45 @@ As described in [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/J
the ES2015 arrow function
in the callback is more succinct than the equivalent function expression and gracefully handles `this`.
回调中所用的 [ES2015 箭头函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
比等价的函数表达式更加简洁,能优雅的处理 *this* 指针。
</div>
The callback sets the component's `heroes` property to the array of heroes returned by the service.
在回调函数中,我们把服务返回的英雄数组赋值给组件的`heroes`属性。
The app is still running, showing a list of heroes, and
responding to a name selection with a detail view.
我们的程序仍在运行,仍在显示英雄列表,在选择英雄时,仍然会把它/她显示在详情页面中。
<div class="l-sub-section">
At the end of this page, [Appendix: take it slow](tutorial/toh-pt4#slow) describes what the app might be like with a poor connection.
查看附录中的“[慢!](tutorial/toh-pt4#slow)”,来了解在较差的网络连接中这个应用会是什么样的。
</div>
## Review the app structure
## 回顾本应用的结构
Verify that you have the following structure after all of your refactoring:
再检查下,经历了本章的所有重构之后,应该有了下列文件结构:
<div class='filetree'>
@ -574,6 +819,8 @@ Verify that you have the following structure after all of your refactoring:
Here are the code files discussed in this page.
下面是本章讨论过的代码文件:
<code-tabs>
@ -594,29 +841,65 @@ Here are the code files discussed in this page.
## The road you've travelled
## 走过的路
Here's what you achieved in this page:
来盘点一下我们完成了什么。
* You created a service class that can be shared by many components.
我们创建了一个能被多个组件共享的服务类。
* You used the `ngOnInit` lifecycle hook to get the hero data when the `AppComponent` activates.
我们使用了`ngOnInit`生命周期钩子,以便在`AppComponent`激活时获取英雄数据。
* You defined the `HeroService` as a provider for the `AppComponent`.
我们把`HeroService`定义为`AppComponent`的一个提供商。
* You created mock hero data and imported them into the service.
我们创建了模拟的英雄数据,并把它导入我们的服务中。
* You designed the service to return a Promise and the component to get the data from the Promise.
我们把服务设计为返回承诺,组件从承诺中获取数据。
Your app should look like this <live-example></live-example>.
现在应用变成了这样:<live-example></live-example>
## The road ahead
## 前方的路
The Tour of Heroes has become more reusable using shared components and services.
The next goal is to create a dashboard, add menu links that route between the views, and format data in a template.
As the app evolves, you'll discover how to design it to make it easier to grow and maintain.
通过使用共享组件和服务,我们的《英雄指南》更有复用性了。
我们还要创建一个仪表盘,要添加在视图间路由的菜单链接,还要在模板中格式化数据。
随着我们应用的进化,我们还会学到如何进行设计,让它更易于扩展和维护。
Read about the Angular component router and navigation among the views in the [next tutorial](tutorial/toh-pt5) page.
我们将在[下一章](tutorial/toh-pt5)学习 Angular 组件路由,以及在视图间导航的知识。
{@a slow}
## Appendix: Take it slow
## 附件:慢
To simulate a slow connection,
import the `Hero` symbol and add the following `getHeroesSlowly()` method to the `HeroService`.
我们可以模拟慢速连接。导入`Hero`类,并且在`HeroService`中添加如下的`getHeroesSlowly()`方法:
<code-example path="toh-4/src/app/hero.service.ts" region="get-heroes-slowly" title="app/hero.service.ts (getHeroesSlowly)" linenums="false">
</code-example>
@ -626,5 +909,10 @@ import the `Hero` symbol and add the following `getHeroesSlowly()` method to the
Like `getHeroes()`, it also returns a `Promise`.
But this Promise waits two seconds before resolving the Promise with mock heroes.
像`getHeroes()`一样,它也返回一个承诺。
但是,这个承诺会在提供模拟数据之前等待两秒钟。
Back in the `AppComponent`, replace `getHeroes()` with `getHeroesSlowly()`
and see how the app behaves.
and see how the app behaves.
回到`AppComponent`,用`heroService.getHeroesSlowly()`替换`heroService.getHeroes()`,并观察应用的行为。

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
HTTP
@intro
Convert the service and components to use Angular's HTTP service.
把服务和组件改为用 Angular 的 HTTP 服务实现
@description
@ -10,26 +10,49 @@ Convert the service and components to use Angular's HTTP service.
In this page, you'll make the following improvements.
在这一章中,我们将进行如下增强:
* Get the hero data from a server.
从服务器获取英雄数据。
* Let users add, edit, and delete hero names.
让用户添加、编辑和删除英雄名。
* Save the changes to the server.
把这些更改保存到服务器。
You'll teach the app to make corresponding HTTP calls to a remote server's web API.
我们要让应用能够对远端服务器提供的Web API发起相应的HTTP调用。
When you're done with this page, the app should look like this <live-example></live-example>.
当我们完成这一章时,应用会变成这样:<live-example></live-example>
## Where you left off
## 延续上一步教程
In the [previous page](tutorial/toh-pt5), you learned to navigate between the dashboard and the fixed heroes list,
editing a selected hero along the way.
That's the starting point for this page.
在[前一章](tutorial/toh-pt5)中,我们学会了在仪表盘和固定的英雄列表之间导航,并编辑选定的英雄。这也就是本章的起点。
## Keep the app transpiling and running
## 保持应用的转译与运行
Enter the following command in the terminal window:
在终端窗口输入如下命令:
<code-example language="sh" class="code-shell">
npm start
@ -41,9 +64,14 @@ Enter the following command in the terminal window:
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
这个命令会在“监听”模式下运行TypeScript编译器当代码变化时它会自动重新编译。
同时,该命令还会在浏览器中启动该应用,并且当代码变化时刷新浏览器。
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
在后续构建《英雄指南》过程中,应用能持续运行,而不用中断服务来编译或刷新浏览器。
<h1>
@ -52,22 +80,41 @@ You can keep building the Tour of Heroes without pausing to recompile or refresh
<h1>
提供 HTTP 服务
</h1>
The `HttpModule` is not a core Angular module.
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
and is shipped in a separate script file as part of the Angular npm package.
`HttpModule`***并不是*** Angular 的核心模块。
它是 Angular 用来进行 Web 访问的一种可选方式,并位于一个名叫 `@angular/http` 的独立附属模块中,并作为 Angular 的 npm 包之一而发布出来。
You're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when you need it.
`systemjs.config`中已经配置好了 *SystemJS*,并在必要时加载它,因此我们已经准备好从`@angular/http`中导入它了。
## Register for HTTP services
## 注册 *HTTP* 服务
The app will depend on the Angular `http` service, which itself depends on other supporting services.
The `HttpModule` from the `@angular/http` library holds providers for a complete set of HTTP services.
我们的应用将会依赖于 Angular 的`http`服务,它本身又依赖于其它支持类服务。
来自`@angular/http`库中的`HttpModule`保存着这些 HTTP 相关服务提供商的全集。
To allow access to these services from anywhere in the app,
add `HttpModule` to the `imports` list of the `AppModule`.
我们要能从本应用的任何地方访问这些服务,就要把`HttpModule`添加到`AppModule`的`imports`列表中。
这里同时也是我们引导应用及其根组件`AppComponent`的地方。
<code-example path="toh-6/src/app/app.module.ts" region="v1" title="src/app/app.module.ts (v1)">
@ -77,18 +124,29 @@ add `HttpModule` to the `imports` list of the `AppModule`.
Notice that you also supply `HttpModule` as part of the *imports* array in root NgModule `AppModule`.
注意,现在`HttpModule`已经是根模块`AppModule`的`imports`数组的一部分了。
## Simulate the web API
## 模拟web API
We recommend registering app-wide services in the root
`AppModule` *providers*.
我们建议在根模块`AppModule`的`providers`数组中注册全应用级的服务。
Until you have a web server that can handle requests for hero data,
the HTTP client will fetch and save data from
a mock service, the *in-memory web API*.
在拥有一个能处理Web请求的服务器之前我们可以先用HTTP客户端通过一个模拟Mock服务内存Web API来获取和保存数据。
Update <code>src/app/app.module.ts</code> with this version, which uses the mock service:
修改<code>src/app/app.module.ts</code>,让它使用这个模拟服务:
<code-example path="toh-6/src/app/app.module.ts" region="v2" title="src/app/app.module.ts (v2)">
@ -100,6 +158,11 @@ Rather than require a real API server, this example simulates communication with
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API">InMemoryWebApiModule</a>
to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative.
导入`InMemoryWebApiModule`并将其加入到模块的`imports`数组。
`InMemoryWebApiModule`将`Http`客户端默认的后端服务 &mdash;
这是一个辅助服务,负责与远程服务器对话 &mdash;
替换成了*内存 Web API*服务:
<code-example path="toh-6/src/app/app.module.ts" region="in-mem-web-api">
@ -111,6 +174,9 @@ The `forRoot()` configuration method takes an `InMemoryDataService` class
that primes the in-memory database.
Add the file `in-memory-data.service.ts` in `app` with the following content:
`forRoot()`配置方法需要`InMemoryDataService`类实例,用来向内存数据库填充数据:
往`app`目录下新增一个文件`in-memory-data.service.ts`,填写下列内容:
<code-example path="toh-6/src/app/in-memory-data.service.ts" region="init" title="src/app/in-memory-data.service.ts" linenums="false">
@ -120,6 +186,8 @@ Add the file `in-memory-data.service.ts` in `app` with the following content:
This file replaces `mock-heroes.ts`, which is now safe to delete.
这个文件已经替换了`mock-heroes.ts`,可以删除`mock-heroes.ts`了。
<div class="alert is-helpful">
@ -129,10 +197,14 @@ The in-memory web API is only useful in the early stages of development and for
Don't worry about the details of this backend substitution; you can
skip it when you have a real web API server.
内存Web API只在开发的早期阶段或写《英雄指南》这样的演示程序时才有用。有了它你将来替换后端实现时就不用关心这些细节问题了。如果你已经有了一个真实的Web API服务器尽管跳过它吧。
Read more about the in-memory web API in the
[Appendix: Tour of Heroes in-memory web api](guide/server-communication#in-mem-web-api)
section of the [HTTP Client](guide/server-communication#in-mem-web-api) page.
关于*内存 Web API* 的更多信息,见 [附录英雄指南Web API](guide/server-communication#in-mem-web-api)中的[HTTP 客户端](guide/server-communication#in-mem-web-api)部分。
</div>
@ -141,8 +213,12 @@ section of the [HTTP Client](guide/server-communication#in-mem-web-api) page.
## Heroes and HTTP
## 英雄与 HTTP
In the current `HeroService` implementation, a Promise resolved with mock heroes is returned.
在目前的`HeroService`的实现中返回的是一个能解析resolve成模拟英雄列表的承诺Promise
<code-example path="toh-4/src/app/hero.service.ts" region="get-heroes" title="src/app/hero.service.ts (old getHeroes)">
@ -153,8 +229,14 @@ In the current `HeroService` implementation, a Promise resolved with mock heroes
This was implemented in anticipation of ultimately
fetching heroes with an HTTP client, which must be an asynchronous operation.
我们返回一个承诺 (Promise),它用模拟版的英雄列表进行解析。
它当时可能看起来显得有点过于复杂,不过我们预料到总有这么一天会通过 HTTP 客户端来获取英雄数据,
而且我们知道,那一定是一个异步操作。
Now convert `getHeroes()` to use HTTP.
现在,我们把`getHeroes()`换成使用 HTTP。
<code-example path="toh-6/src/app/hero.service.ts" region="getHeroes" title="src/app/hero.service.ts (updated getHeroes and new class members)">
@ -164,6 +246,8 @@ Now convert `getHeroes()` to use HTTP.
Update the import statements as follows:
更新后的导入声明如下:
<code-example path="toh-6/src/app/hero.service.ts" region="imports" title="src/app/hero.service.ts (updated imports)">
@ -174,6 +258,8 @@ Update the import statements as follows:
Refresh the browser. The hero data should successfully load from the
mock server.
刷新浏览器后,英雄数据就会从模拟服务器被成功读取。
<h3 id="http-promise">HTTP Promise</h3>
@ -181,8 +267,14 @@ The Angular `http.get` returns an RxJS `Observable`.
*Observables* are a powerful way to manage asynchronous data flows.
You'll read about [Observables](tutorial/toh-pt6#observables) later in this page.
Angular 的`http.get`返回一个 RxJS 的`Observable`对象。
*Observable可观察对象*是一个管理异步数据流的强力方式。
后面我们还会进一步学习[可观察对象](tutorial/toh-pt6#observables)。
For now, you've converted the `Observable` to a `Promise` using the `toPromise` operator.
*现在*,我们先利用`toPromise`操作符把`Observable`直接转换成`Promise`对象,回到已经熟悉的地盘。
<code-example path="toh-6/src/app/hero.service.ts" region="to-promise">
@ -192,10 +284,16 @@ For now, you've converted the `Observable` to a `Promise` using the `toPromise`
The Angular `Observable` doesn't have a `toPromise` operator out of the box.
不幸的是Angular 的`Observable`并没有一个`toPromise`操作符... 没有打包在一起发布。Angular的`Observable`只是一个骨架实现。
There are many operators like `toPromise` that extend `Observable` with useful capabilities.
To use those capabilities, you have to add the operators themselves.
That's as easy as importing them from the RxJS library like this:
有很多像`toPromise`这样的操作符,用于扩展`Observable`,为其添加有用的能力。
如果我们希望得到那些能力,就得自己添加那些操作符。
那很容易,只要从 RxJS 库中导入它们就可以了,就像这样:
<code-example path="toh-6/src/app/hero.service.ts" region="rxjs">
@ -208,6 +306,8 @@ That's as easy as importing them from the RxJS library like this:
You'll add more operators, and learn why you must do so, [later in this tutorial](tutorial/toh-pt6#rxjs-imports).
我们还要添加更多的操作符,并且必须这么做,要了解其中的原因,参见[本章稍后的部分](tutorial/toh-pt6#rxjs-imports)。
</div>
@ -216,8 +316,12 @@ You'll add more operators, and learn why you must do so, [later in this tutorial
### Extracting the data in the *then* callback
In the *Promise*'s `then()` callback, you call the `json` method of the HTTP `Response` to extract the
### 在 *then* 回调中提取出数据
In the *Promise*'s `then()` callback , you call the `json` method of the HTTP `Response` to extract the
data within the response.
*promise* 的`then()`回调中,我们调用 HTTP 的`Reponse`对象的`json`方法,以提取出其中的数据。
<code-example path="toh-6/src/app/hero.service.ts" region="to-data">
@ -230,6 +334,10 @@ The response JSON has a single `data` property, which
holds the array of heroes that the caller wants.
So you grab that array and return it as the resolved Promise value.
这个由`json`方法返回的对象只有一个`data`属性。
这个`data`属性保存了*英雄*数组,这个数组才是调用者真正想要的。
所以我们取得这个数组,并且把它作为承诺的值进行解析。
<div class="alert is-important">
@ -239,6 +347,10 @@ Note the shape of the data that the server returns.
This particular in-memory web API example returns an object with a `data` property.
Your API might return something else. Adjust the code to match your web API.
仔细看看这个由服务器返回的数据的形态。
这个*内存 Web API* 的范例中所做的是返回一个带有`data`属性的对象。
你的 API 也可以返回其它东西。请调整这些代码以匹配*你的 Web API*。
</div>
@ -247,10 +359,19 @@ Your API might return something else. Adjust the code to match your web API.
The caller is unaware that you fetched the heroes from the (mock) server.
It receives a Promise of *heroes* just as it did before.
调用者并不知道这些实现机制,它仍然像以前那样接收一个包含*英雄数据*的承诺。
它也不知道我们已经改成了从服务器获取英雄数据。
它也不必了解把 HTTP 响应转换成英雄数据时所作的这些复杂变换。
看到美妙之处了吧,这正是将数据访问委托组一个服务的目的。
### Error Handling
### 错误处理
At the end of `getHeroes()`, you `catch` server failures and pass them to an error handler.
在`getHeroes()`的最后,我们`catch`了服务器的失败信息,并把它们传给了错误处理器:
<code-example path="toh-6/src/app/hero.service.ts" region="catch">
@ -261,6 +382,9 @@ At the end of `getHeroes()`, you `catch` server failures and pass them to an err
This is a critical step.
You must anticipate HTTP failures, as they happen frequently for reasons beyond your control.
这是一个关键的步骤!
我们必须预料到 HTTP 请求会失败,因为有太多我们无法控制的原因可能导致它们频繁出现各种错误。
<code-example path="toh-6/src/app/hero.service.ts" region="handleError">
@ -271,20 +395,34 @@ You must anticipate HTTP failures, as they happen frequently for reasons beyond
This demo service logs the error to the console; in real life,
you would handle the error in code. For a demo, this works.
在这个范例服务中,我们把错误记录到控制台中;在真实世界中,我们应该用代码对错误进行处理。但对于演示来说,这就够了。
The code also includes an error to
the caller in a rejected promise, so that the caller can display a proper error message to the user.
我们还要通过一个被拒绝 (rejected) 的承诺来把该错误用一个用户友好的格式返回给调用者,
以便调用者能把一个合适的错误信息显示给用户。
### Get hero by id
### 通过id获取英雄
When the `HeroDetailComponent` asks the `HeroService` to fetch a hero,
the `HeroService` currently fetches all heroes and
filters for the one with the matching `id`.
That's fine for a simulation, but it's wasteful to ask a real server for all heroes when you only want one.
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (such as `api/hero/11`).
当`HeroDetailComponent`向`HeroService`请求获取一个英雄时,`HeroService`会获取所有英雄,并从中过滤出与`id`匹配的那一个。
这对于例子来说倒是无可厚非,
不过在真实服务中,这种为了获取一个英雄而请求全部英雄的做法就有点浪费了,
许多Web API支持*get-by-id*请求,形如:`api/hero/:id`(如:`api/hero/11`)。
Update the `HeroService.getHero()` method to make a _get-by-id_ request:
修改 `HeroService.getHero()` 方法来发起一个 *get-by-id* 请求:
<code-example path="toh-6/src/app/hero.service.ts" region="getHero" title="src/app/hero.service.ts">
</code-example>
@ -294,36 +432,63 @@ Update the `HeroService.getHero()` method to make a _get-by-id_ request:
This request is almost the same as `getHeroes()`.
The hero id in the URL identifies which hero the server should update.
此方法基本上与`getHeroes`方法一致通过在URL中添加英雄的id来告诉服务器应该获取_那个_英雄
匹配`api/hero/:id`模式。
Also, the `data` in the response is a single hero object rather than an array.
我们还要把响应中返回的`data`改为一个英雄对象,而不再是对象数组。组。
### Unchanged _getHeroes_ API
### `getHeroes` API 没变
Although you made significant internal changes to `getHeroes()` and `getHero()`,
the public signatures didn't change.
You still return a Promise from both methods.
You won't have to update any of the components that call them.
尽管我们在`getHeroes()`和`getHero()`方法的*内部*做了重大修改,
但是他们的公共签名却没有变。这两个方法仍然返回的是一个Promise对象
所以并不需要修改任何调用他们的组件。
Now it's time to add the ability to create and delete heroes.
现在,我们该支持创建和删除英雄了。
## Updating hero details
## 更新英雄详情
Try editing a hero's name in the hero detail view.
As you type, the hero name is updated in the view heading.
But if you click the Back button, the changes are lost.
我们已经可以在英雄详情中编辑英雄的名字了。来试试吧。在输入的时候,页头上的英雄名字也会随之更新。
不过当我们点了`Back后退`按钮时,这些修改就丢失了。
Updates weren't lost before. What changed?
When the app used a list of mock heroes, updates were applied directly to the
hero objects within the single, app-wide, shared list. Now that you're fetching data
from a server, if you want changes to persist, you must write them back to
the server.
以前是不会丢失更新的,怎么回事?
当该应用使用模拟出来的英雄列表时,修改的是一份全局共享的英雄列表,而现在改成了从服务器获取数据。
如果我们希望这些更改被持久化,我们就得把它们写回服务器。
### Add the ability to save hero details
### 保存英雄详情
At the end of the hero detail template, add a save button with a `click` event
binding that invokes a new component method named `save()`.
我们先来确保对英雄名字的编辑不会丢失。先在英雄详情模板的底部添加一个保存按钮,它绑定了一个`click`事件,事件绑定会调用组件中一个名叫`save()`的新方法:
<code-example path="toh-6/src/app/hero-detail.component.html" region="save" title="src/app/hero-detail.component.html (save)">
@ -334,6 +499,8 @@ binding that invokes a new component method named `save()`.
Add the following `save()` method, which persists hero name changes using the hero service
`update()` method and then navigates back to the previous view.
`save()`方法使用 hero 服务的`update()`方法来持久化对英雄名字的修改,然后导航回前一个视图:
<code-example path="toh-6/src/app/hero-detail.component.ts" region="save" title="src/app/hero-detail.component.ts (save)">
@ -343,9 +510,12 @@ Add the following `save()` method, which persists hero name changes using the he
### Add a hero service _update()_ method
### hero 服务的`update`方法
The overall structure of the `update()` method is similar to that of
`getHeroes()`, but it uses an HTTP `put()` to persist server-side changes.
`update()`方法的大致结构与`getHeroes()`类似,不过我们使用 HTTP 的 `put()` 方法来把修改持久化到服务端:
<code-example path="toh-6/src/app/hero.service.ts" region="update" title="src/app/hero.service.ts (update)">
@ -359,19 +529,30 @@ the URL. The `put()` body is the JSON string encoding of the hero, obtained by
calling `JSON.stringify`. The body content type
(`application/json`) is identified in the request header.
我们通过一个编码在 URL 中的英雄 `id` 来告诉服务器应该更新哪个英雄。`put` 的 body 是该英雄的 JSON 字符串,它是通过调用`JSON.stringify`得到的。
并且在请求头中标记出的 body 的内容类型(`application/json`)。
Refresh the browser, change a hero name, save your change,
and click the browser Back button. Changes should now persist.
刷新浏览器试一下,对英雄名字的修改确实已经被持久化了。
## Add the ability to add heroes
## 添加英雄
To add a hero, the app needs the hero's name. You can use an `input`
element paired with an add button.
要添加一个新的英雄,我们得先知道英雄的名字。我们使用一个 `input` 元素和一个添加按钮来实现。
Insert the following into the heroes component HTML, just after
the heading:
把下列代码插入 heroes 组件的 HTML 中,放在标题的下面:
<code-example path="toh-6/src/app/heroes.component.html" region="add" title="src/app/heroes.component.html (add)">
@ -382,6 +563,8 @@ the heading:
In response to a click event, call the component's click handler and then
clear the input field so that it's ready for another name.
当点击事件触发时,我们调用组件的点击处理器,然后清空这个输入框,以便用来输入另一个名字。
<code-example path="toh-6/src/app/heroes.component.ts" region="add" title="src/app/heroes.component.ts (add)">
@ -392,8 +575,14 @@ clear the input field so that it's ready for another name.
When the given name is non-blank, the handler delegates creation of the
named hero to the hero service, and then adds the new hero to the array.
当指定的名字不为空的时候,点击处理器就会委托 hero 服务来创建一个具有此名字的英雄,
并把这个新的英雄添加到我们的数组中。
Implement the `create()` method in the `HeroService` class.
然后,我们在`HeroService`类中实现这个`create()`方法。
<code-example path="toh-6/src/app/hero.service.ts" region="create" title="src/app/hero.service.ts (create)">
</code-example>
@ -402,15 +591,23 @@ Implement the `create()` method in the `HeroService` class.
Refresh the browser and create some heroes.
刷新浏览器,并创建一些新的英雄!
## Add the ability to delete a hero
## 支持添加英雄
Each hero in the heroes view should have a delete button.
在英雄列表视图中的每个英雄都应该有一个删除按钮。
Add the following button element to the heroes component HTML, after the hero
name in the repeated `<li>` element.
把这个按钮元素添加到英雄列表组件的 HTML 中,把它放在`<li>`标签中的英雄名的后面:
<code-example path="toh-6/src/app/heroes.component.html" region="delete">
@ -420,6 +617,8 @@ name in the repeated `<li>` element.
The `<li>` element should now look like this:
`<li>`元素应该变成了这样:
<code-example path="toh-6/src/app/heroes.component.html" region="li-element" title="src/app/heroes.component.html (li-element)">
@ -432,8 +631,13 @@ click handler code stops the propagation of the click event&mdash;you
don't want the `<li>` click handler to be triggered because doing so would
select the hero that the user will delete.
除了调用组件的`delete()`方法之外,这个删除按钮的点击处理器还应该阻止点击事件向上冒泡 &mdash;
我们并不希望触发`<li>`的事件处理器,否则它会选中我们要删除的这位英雄。
The logic of the `delete()` handler is a bit trickier:
`delete()`处理器的逻辑略复杂:
<code-example path="toh-6/src/app/heroes.component.ts" region="delete" title="src/app/heroes.component.ts (delete)">
@ -445,10 +649,16 @@ Of course you delegate hero deletion to the hero service, but the component
is still responsible for updating the display: it removes the deleted hero
from the array and resets the selected hero, if necessary.
当然,我们仍然把删除英雄的操作委托给了 hero 服务,
不过该组件仍然负责更新显示:它从数组中移除了被删除的英雄,如果删除的是正选中的英雄,还会清空选择。
To place the delete button at the far right of the hero entry,
add this CSS:
我们希望删除按钮被放在英雄条目的最右边。
于是 CSS 变成了这样:
<code-example path="toh-6/src/app/heroes.component.css" region="additions" title="src/app/heroes.component.css (additions)">
@ -458,8 +668,12 @@ add this CSS:
### Hero service _delete()_ method
### hero 服务的`delete()`方法
Add the hero service's `delete()` method, which uses the `delete()` HTTP method to remove the hero from the server:
hero 服务的`delete()`方法使用 HTTP 的 `delete()` 方法来从服务器上移除该英雄:
<code-example path="toh-6/src/app/hero.service.ts" region="delete" title="src/app/hero.service.ts (delete)">
@ -469,6 +683,8 @@ Add the hero service's `delete()` method, which uses the `delete()` HTTP method
Refresh the browser and try the new delete functionality.
刷新浏览器,并试一下这个新的删除功能。
<div id='observables'>
@ -478,41 +694,76 @@ Refresh the browser and try the new delete functionality.
## Observables
## 可观察对象 (Observable)
Each `Http` service method returns an `Observable` of HTTP `Response` objects.
`Http`服务中的每个方法都返回一个 HTTP `Response`对象的`Observable`实例。
The `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
This section shows you how, when, and why to return the `Observable` directly.
我们的`HeroService`中把那个`Observable`对象转换成了`Promise`(承诺),并把这个承诺返回给了调用者。
这一节,我们将学会直接返回`Observable`,并且讨论何时以及为何那样做会更好。
### Background
### 背景
An *Observable* is a stream of events that you can process with array-like operators.
Angular core has basic support for observables.
一个*可观察对象*是一个事件流,我们可以用数组型操作符来处理它。
Angular core has basic support for observables.
Developers augment that support with operators and extensions from the
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>.
You'll see how shortly.
Recall that the `HeroService` chained the `toPromise` operator to the `Observable` result of `http.get()`.
Angular 内核中提供了对可观察对象的基本支持。而我们这些开发人员可以自己从 <a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS</a> 库中引入操作符和扩展。
我们会简短的讲解下如何做。
Recall that the `HeroService` chained the `toPromise` operator to the `Observable` result of `http.get()`.
That operator converted the `Observable` into a `Promise` and you passed that promise back to the caller.
快速回忆一下`HeroService`,它在`http.get()`返回的`Observable`后面串联了一个`toPromise`操作符。
该操作符把`Observable`转换成了`Promise`,并且我们把那个承诺返回给了调用者。
Converting to a Promise is often a good choice. You typically ask `http.get()` to fetch a single chunk of data.
When you receive the data, you're done.
The calling component can easily consume a single result in the form of a Promise.
转换成承诺通常是更好地选择,我们通常会要求`http.get()`获取单块数据。只要接收到数据,就算完成。
使用承诺这种形式的结果是让调用方更容易写,并且承诺已经在 JavaScript 程序员中被广泛接受了。
But requests aren't always done only once.
You may start one request,
cancel it, and make a different request before the server has responded to the first request.
但是请求并非总是“一次性”的。我们可以开始一个请求,
并且取消它,在服务器对第一个请求作出回应之前,再开始另一个不同的请求 。
像这样一个_请求-取消-新请求_的序列用*承诺*是很难实现的,但接下来我们会看到,它对于*可观察对象*却很简单。
A *request-cancel-new-request* sequence is difficult to implement with `Promise`s, but
easy with `Observable`s.
*请求-取消-新请求*的序列对于`Promise`来说是很难实现的,但是对`Observable`来说则很容易。
### Add the ability to search by name
### 支持按名搜索
You're going to add a *hero search* feature to the Tour of Heroes.
As the user types a name into a search box, you'll make repeated HTTP requests for heroes filtered by that name.
我们要为《英雄指南》添加一个*英雄搜索*特性。
当用户在搜索框中输入一个名字时,我们将不断发起 HTTP 请求,以获得按名字过滤的英雄。
Start by creating `HeroSearchService` that sends search queries to the server's web API.
我们先创建`HeroSearchService`服务,它会把搜索请求发送到我们服务器上的 Web API。
<code-example path="toh-6/src/app/hero-search.service.ts" title="src/app/hero-search.service.ts">
@ -523,20 +774,33 @@ Start by creating `HeroSearchService` that sends search queries to the server's
The `http.get()` call in `HeroSearchService` is similar to the one
in the `HeroService`, although the URL now has a query string.
`HeroSearchService`中的`http.get()`调用和`HeroService`中的很相似,只是这次带了查询字符串。
More importantly, you no longer call `toPromise()`.
Instead you return the *Observable* from the the `http.get()`,
after chaining it to another RxJS operator, <code>map()</code>,
to extract heroes from the response data.
Instead you return the *Observable* from the the `http.get()`,
after chaining it to another RxJS operator, <code>map()</code>,
to extract heroes from the response data.
更重要的是:我们不再调用`toPromise`方法,而是从`http.get`
方法中返回一个*Observable*对象之后调用RxJS的<code>map</code>操作符
来从返回数据中提取英雄。
RxJS operator chaining makes response processing easy and readable.
See the [discussion below about operators](tutorial/toh-pt6#rxjs-imports).</span>
See the [discussion below about operators](tutorial/toh-pt6#rxjs-imports).
链式RxJS操作可以让我们简单、易读的处理响应数据。详见[下面关于操作符的讨论](tutorial/toh-pt6#rxjs-imports)
### HeroSearchComponent
Create a `HeroSearchComponent` that calls the new `HeroSearchService`.
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
我们再创建一个新的`HeroSearchComponent`来调用这个新的`HeroSearchService`。
The component template is simple&mdash;just a text box and a list of matching search results.
组件模板很简单,就是一个输入框和一个显示匹配的搜索结果的列表。
<code-example path="toh-6/src/app/hero-search.component.html" title="src/app/hero-search.component.html">
@ -546,6 +810,9 @@ The component template is simple&mdash;just a text box and a list of matching se
Also, add styles for the new component.
我们还要往这个新组件中添加样式。
<code-example path="toh-6/src/app/hero-search.component.css" title="src/app/hero-search.component.css">
</code-example>
@ -555,14 +822,24 @@ Also, add styles for the new component.
As the user types in the search box, a *keyup* event binding calls the component's `search()`
method with the new search box value.
当用户在搜索框中输入时,一个 *keyup* 事件绑定会调用该组件的`search()`方法,并传入新的搜索框的值。
As expected, the `*ngFor` repeats hero objects from the component's `heroes` property.
不出所料,`*ngFor`从该组件的`heroes`属性重复获取 *hero* 对象。这也没啥特别的。
But as you'll soon see, the `heroes` property is now an *Observable* of hero arrays, rather than just a hero array.
The `*ngFor` can't do anything with an `Observable` until you route it through the `async` pipe (`AsyncPipe`).
The `async` pipe subscribes to the `Observable` and produces the array of heroes to `*ngFor`.
但是,接下来我们看到`heroes`属性现在是英雄列表的`Observable`对象,而不再只是英雄数组。
`*ngFor`不能用可观察对象做任何事,除非我们在它后面跟一个`async` pipe (`AsyncPipe`)。
这个`async`管道会订阅到这个可观察对象,并且为`*ngFor`生成一个英雄数组。
Create the `HeroSearchComponent` class and metadata.
该创建`HeroSearchComponent`类及其元数据了。
<code-example path="toh-6/src/app/hero-search.component.ts" title="src/app/hero-search.component.ts">
@ -572,8 +849,12 @@ Create the `HeroSearchComponent` class and metadata.
#### Search terms
#### 搜索词
Focus on the `searchTerms`:
仔细看下这个`searchTerms`
<code-example path="toh-6/src/app/hero-search.component.ts" region="searchTerms">
@ -584,7 +865,10 @@ Focus on the `searchTerms`:
A `Subject` is a producer of an _observable_ event stream;
`searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
Each call to `search()` puts a new string into this subject's _observable_ stream by calling `next()`.
`Subject`主题是一个_可观察的_事件流中的生产者。
`searchTerms`生成一个产生字符串的`Observable`用作按名称搜索时的过滤条件。Each call to `search()` puts a new string into this subject's _observable_ stream by calling `next()`.
每当调用`search()`时都会调用`next()`来把新的字符串放进该主题的_可观察_流中。
{@a ngoninit}
@ -592,10 +876,15 @@ Each call to `search()` puts a new string into this subject's _observable_ strea
#### Initialize the *heroes* property (*ngOnInit*)
#### 初始化 *heroes* 属性(*ngOnInit*)
A `Subject` is also an `Observable`.
You can turn the stream
of search terms into a stream of `Hero` arrays and assign the result to the `heroes` property.
`Subject`也是一个`Observable`对象。
我们要把搜索词的流转换成`Hero`数组的流,并把结果赋值给`heroes`属性。
<code-example path="toh-6/src/app/hero-search.component.ts" region="search">
@ -606,16 +895,33 @@ of search terms into a stream of `Hero` arrays and assign the result to the `her
Passing every user keystroke directly to the `HeroSearchService` would create an excessive amount of HTTP requests,
taxing server resources and burning through the cellular network data plan.
如果我们直接把每一次用户按键都直接传给`HeroSearchService`,就会发起一场 HTTP 请求风暴。
这可不好玩。我们不希望占用服务器资源,也不想耗光蜂窝移动网络的流量。
Instead, you can chain `Observable` operators that reduce the request flow to the string `Observable`.
You'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
不过,我们可以在字符串的`Observable`后面串联一些`Observable`操作符,来归并这些请求。
我们将对`HeroSearchService`发起更少的调用,并且仍然获得足够及时的响应。做法如下:
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
before passing along the latest string. You'll never make requests more frequently than 300ms.
在传出最终字符串之前,`debounceTime(300)`将会等待,直到新增字符串的事件暂停了 300 毫秒。
我们实际发起请求的间隔永远不会小于 300ms。
* `distinctUntilChanged` ensures that a request is sent only if the filter text changed.
`distinctUntilChanged`确保只在过滤条件变化时才发送请求,
这样就不会重复请求同一个搜索词了。
* `switchMap()` calls the search service for each search term that makes it through `debounce` and `distinctUntilChanged`.
It cancels and discards previous search observables, returning only the latest search service observable.
`switchMap()`会为每个从`debounce`和`distinctUntilChanged`中通过的搜索词调用搜索服务。
它会取消并丢弃以前的搜索可观察对象,只保留最近的。
<div class="l-sub-section">
@ -627,16 +933,31 @@ every qualifying key event can trigger an `http()` method call.
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
and they may not return in the order sent.
借助[switchMap操作符](http://www.learnrxjs.io/operators/transformation/switchmap.html)
(正式名称是`flatMapLatest`)
每次符合条件的按键事件都会触发一次对`http()`方法的调用。即使在发送每个请求前都有 300 毫秒的延迟,
我们仍然可能同时拥有多个在途的 HTTP 请求,并且它们返回的顺序未必就是发送时的顺序。
`switchMap()` preserves the original request order while returning
only the observable from the most recent `http` method call.
only the observable from the most recent `http` method call.
Results from prior calls are canceled and discarded.
`switchMap()`保留了原始的请求顺序,并且只返回最近一次 `http` 调用返回的可观察对象。
这是因为以前的调用都被取消或丢弃了。
If the search text is empty, the `http()` method call is also short circuited
and an observable containing an empty array is returned.
Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable
如果搜索框为空,我们还可以短路掉这次`http()`方法调用,并且直接返回一个包含空数组的可观察对象。
Note that until the service supports that feature,_canceling_ the `HeroSearchService` Observable
doesn't actually abort a pending HTTP request.
For now, unwanted results are discarded.
For now , unwanted resultsare discarded.
注意,*取消*`HeroSearchService`的可观察对象并不会实际中止 (abort) 一个未完成的 HTTP 请求,
除非服务支持这个特性,这个问题我们以后再讨论。
目前我们的做法只是丢弃不希望的结果。
</div>
@ -644,7 +965,9 @@ For now, unwanted results are discarded.
* `catch` intercepts a failed observable.
The simple example prints the error to the console; a real life app would do better.
Then to clear the search result, you return an observable containing an empty array.
Then to clear the search result, you return an observable containing an empty array .
`catch`拦截失败的可观察对象。这个简单的例子中只是把错误信息打印到控制台(但实际的应用需要做更多事),然后返回一个包含空数组的可观察对象,以清空搜索结果。
{@a rxjs-imports}
@ -652,12 +975,19 @@ Then to clear the search result, you return an observable containing an empty ar
### Import RxJS operators
### 导入 RxJS 操作符
Most RxJS operators are not included in Angular's base `Observable` implementation.
The base implementation includes only what Angular itself requires.
大部分RxJS操作符都不包括在Angular的`Observable`基本实现中基本实现只包括Angular本身所需的功能。
When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined.
Here are all the RxJS imports that _this_ component needs:
如果想要更多的RxJS功能我们必须*导入*其所定义的库来扩展`Observable`对象,
以下是*这个*模块所需导入的所有RxJS操作符
<code-example path="toh-6/src/app/hero-search.component.ts" region="rxjs-imports" title="src/app/hero-search.component.ts (rxjs imports)" linenums="false">
@ -668,15 +998,24 @@ Here are all the RxJS imports that _this_ component needs:
The `import 'rxjs/add/...'` syntax may be unfamiliar.
It's missing the usual list of symbols between the braces: `{...}`.
你可能并不熟悉这种`import 'rxjs/add/...'`语法,它缺少了花括号中的导入列表:`{...}`。
You don't need the operator symbols themselves.
In each case, the mere act of importing the library
loads and executes the library's script file which, in turn, adds the operator to the `Observable` class.
这是因为我们并不需要操作符本身,这种情况下,我们所做的其实是导入这个库,加载并运行其中的脚本,
它会把操作符添加到`Observable`类中。
### Add the search component to the dashboard
### 为仪表盘添加搜索组件
Add the hero search HTML element to the bottom of the `DashboardComponent` template.
将表示“英雄搜索”组件的 HTML 元素添加到`DashboardComponent`模版的最后面。
<code-example path="toh-6/src/app/dashboard.component.html" title="src/app/dashboard.component.html" linenums="false">
@ -688,6 +1027,8 @@ Finally, import `HeroSearchComponent` from
<code>hero-search.component.ts</code>
and add it to the `declarations` array.
最后,从<span ngio-ex>hero-search.component.ts</span>中导入`HeroSearchComponent`并将其添加到`declarations`数组中。
<code-example path="toh-6/src/app/app.module.ts" region="search" title="src/app/app.module.ts (search)">
@ -698,6 +1039,9 @@ and add it to the `declarations` array.
Run the app again. In the Dashboard, enter some text in the search box.
If you enter characters that match any existing hero names, you'll see something like this.
再次运行该应用,跳转到*仪表盘*,并在英雄下方的搜索框里输入一些文本。
运行效果如下:
<figure class='image-display'>
<img src='assets/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component"></img>
@ -708,9 +1052,14 @@ If you enter characters that match any existing hero names, you'll see something
## App structure and code
## 应用的结构与代码
Review the sample source code in the <live-example></live-example> for this page.
Verify that you have the following structure:
回顾一下本章<live-example></live-example>中的范例代码。
验证我们是否得到了如下结构:
<div class='filetree'>
@ -853,16 +1202,40 @@ Verify that you have the following structure:
## Home Stretch
## 最后冲刺
You're at the end of your journey, and you've accomplished a lot.
旅程即将结束,不过我们已经收获颇丰。
* You added the necessary dependencies to use HTTP in the app.
我们添加了在应用程序中使用 HTTP 的必备依赖。
* You refactored `HeroService` to load heroes from a web API.
我们重构了`HeroService`,以通过 web API 来加载英雄数据。
* You extended `HeroService` to support `post()`, `put()`, and `delete()` methods.
我们扩展了`HeroService`来支持 `post()`、`put()` 和 `delete()` 方法。
* You updated the components to allow adding, editing, and deleting of heroes.
我们更新了组件,以允许用户添加、编辑和删除英雄。
* You configured an in-memory web API.
我们配置了一个内存 Web API。
* You learned how to use Observables.
我们学会了如何使用“可观察对象”。
Here are the files you added or changed in this page.
下面是我们**添加或修改**之后的文件汇总。
<code-tabs>
@ -934,8 +1307,12 @@ Here are the files you added or changed in this page.
## Next step
## 下一步
Return to the [learning path](guide/learning-angular#architecture), where
you can read more about the concepts and practices found in this tutorial.
返回[学习路径](guide/learning-angular#architecture),你可以阅读在本教程中探索到的概念和实践。
</div>