fix: 翻译了 toh-pt0 ~ toh-pt4

This commit is contained in:
Zhicheng Wang 2018-03-08 17:07:55 +08:00
parent 09d93793ff
commit 45a14d1cc0
6 changed files with 671 additions and 35 deletions

View File

@ -32,26 +32,22 @@
"tooltip": "Angular 开发文档",
"hidden": true
},
{
"url": "guide/docs-style-guide",
"title": "文档风格指南",
"tooltip": "给文档作者的风格指南",
"hidden": true
},
{
"url": "guide/webpack",
"title": "Webpack: 简介",
"hidden": true
},
{
"url": "guide/quickstart",
"title": "快速上手",
"tooltip": "Angular 破冰"
},
{
"title": "教程",
"tooltip": "此《英雄指南》教程会带你用 TypeScript 一步步创建一个 Angular 应用。",
@ -78,7 +74,7 @@
},
{
"url": "tutorial/toh-pt3",
"title": "5. 主从结构",
"title": "5. 主从组件",
"tooltip": "第五部分:把主从结构的页面重构成多个组件"
},
{
@ -98,7 +94,6 @@
}
]
},
{
"title": "核心知识",
"tooltip": "学习 Angular 的核心知识",
@ -226,14 +221,12 @@
}
]
},
{
"url": "guide/bootstrapping",
"title": "引导启动",
"tooltip": "在应用的根模块AppModule中告诉 Angular 如何构造并引导引用。"
},
{
"title": "NgModules",
"tooltip": "NgModules.",
"children": [
@ -297,8 +290,8 @@
"title": "NgModule 常见问题",
"tooltip": "回答关于 NgModules 的常见问题。"
}
]},
]
},
{
"title": "依赖注入",
"tooltip": "依赖注入:创建并注入各种服务。",
@ -325,7 +318,6 @@
}
]
},
{
"url": "guide/http",
"title": "HttpClient",
@ -346,13 +338,12 @@
"title": "速查表",
"tooltip": "关于 Angular 常用编码技术的快速指南。"
}
]},
]
},
{
"title": "其它技术",
"tooltip": "把 Angular 用到你的实际工作中的一些技巧",
"children": [
{
"url": "guide/i18n",
"title": "国际化 (i18n)",
@ -392,7 +383,6 @@
"title": "npm 包",
"tooltip": "建议的 npm 包,以及如何指定包的依赖。"
},
{
"url": "guide/typescript-configuration",
"title": "TypeScript 配置",
@ -441,7 +431,6 @@
}
]
},
{
"title": "升级",
"tooltip": "渐进式的把 AngularJS 应用升级到 Angular。",
@ -480,7 +469,6 @@
}
]
},
{
"title": "API 参考手册",
"tooltip": "关于 Angular 中类和值的详细信息。",
@ -493,7 +481,6 @@
"hidden": true
}
],
"Footer": [
{
"title": "资源",
@ -585,11 +572,18 @@
]
}
],
"docVersions": [
{ "title": "v4 (LTS)", "url": "https://v4.angular.io" },
{ "title": "v2", "url": "https://v2.angular.cn" },
{ "title": "AngularDart", "url": "https://webdev.dartlang.org/angular" }
{
"title": "v4 (LTS)",
"url": "https://v4.angular.io"
},
{
"title": "v2",
"url": "https://v2.angular.cn"
},
{
"title": "AngularDart",
"url": "https://webdev.dartlang.org/angular"
}
]
}

View File

@ -130,7 +130,7 @@ This interpolation binding presents the component's `title` property value
inside the HTML header tag.
双花括号语法是 Angular 的*插值绑定*语法。
这个插值绑定的意思是把组件的 `title` 属性的值绑定到 HTML 中的 header 标记中。
这个插值绑定的意思是把组件的 `title` 属性的值绑定到 HTML 中的 `h1` 标记中。
The browser refreshes and displays the new application title.
@ -160,7 +160,7 @@ Here's an excerpt from the `styles.css` for the _Tour of Heroes_ sample app.
## Final code review
## 最终代码
## 查看最终代码
The source code for this tutorial and the complete _Tour of Heroes_ global styles
are available in the <live-example></live-example>.

View File

@ -15,6 +15,8 @@ and place that component in the application shell.
Using the Angular CLI, generate a new component named `heroes`.
使用 Angular CLI 创建一个名为 `heroes` 的新组件。
<code-example language="sh" class="code-shell">
ng generate component heroes
@ -24,8 +26,12 @@ Using the Angular CLI, generate a new component named `heroes`.
The CLI creates a new folder, `src/app/heroes/` and generates
the three files of the `HeroesComponent`.
CLI 创建了一个新的文件夹 `src/app/heroes/`,并生成了 `HeroesComponent` 的三个文件。
The `HeroesComponent` class file is as follows:
`HeroesComponent` 的类文件如下:
<code-example
path="toh-pt1/src/app/heroes/heroes.component.ts" region="v1"
title="app/heroes/heroes.component.ts (initial version)" linenums="false">
@ -35,52 +41,85 @@ The `HeroesComponent` class file is as follows:
You always import the `Component` symbol from the Angular core library
and annotate the component class with `@Component`.
你要从 Angular 核心库中导入 `Component` 符号,并为组件类加上 `@Component` 装饰器。
`@Component` is a decorator function that specifies the Angular metadata for the component.
`@Component` 是个装饰器函数,用于为该组件指定 Angular 所需的元数据。
The CLI generated three metadata properties:
CLI 自动生成了三个元数据属性:
1. `selector`&mdash; the component's CSS element selector
`selector`&mdash; 组件的选择器CSS 元素选择器)
1. `templateUrl`&mdash; the location of the component's template file.
`templateUrl`&mdash; 组件模板文件的位置。
1. `styleUrls`&mdash; the location of the component's private CSS styles.
`styleUrls`&mdash; 组件私有 CSS 样式表文件的位置。
{@a selector}
The [CSS element selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors),
`'app-heroes'`, matches the name of the HTML element that identifies this component within a parent component's template.
[CSS 元素选择器](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors) `app-heroes` 用来在父组件的模板中匹配 HTML 元素的名称,以识别出该组件。
The `ngOnInit` is a [lifecycle hook](guide/lifecycle-hooks#oninit)
Angular calls `ngOnInit` shortly after creating a component.
It's a good place to put initialization logic.
`ngOnInit` 是一个[生命周期钩子](guide/lifecycle-hooks#oninit)Angular 在创建完组件后很快就会调用 `ngOnInit`。这里是放置初始化逻辑的好地方。
Always `export` the component class so you can `import` it elsewhere ... like in the `AppModule`.
始终要 `export` 这个组件类,以便在其它地方(比如 `AppModule`)导入它。
### Add a _hero_ property
### 添加 `hero` 属性
Add a `hero` property to the `HeroesComponent` for a hero named "Windstorm."
`HeroesComponent` 中添加一个 `hero` 属性,用来表示一个名叫 “Windstorm” 的英雄。
<code-example path="toh-pt1/src/app/heroes/heroes.component.ts" region="add-hero" title="heroes.component.ts (hero property)" linenums="false">
</code-example>
### Show the hero
### 显示英雄
Open the `heroes.component.html` template file.
Delete the default text generated by the Angular CLI and
replace it with a data binding to the new `hero` property.
打开模板文件 `heroes.component.html`。删除 Angular CLI 自动生成的默认内容,改为到 `hero` 属性的数据绑定。
<code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" title="heroes.component.html" region="show-hero-1" linenums="false">
</code-example>
## Show the _HeroesComponent_ view
## 显示 `HeroesComponent` 视图
To display the `HeroesComponent`, you must add it to the template of the shell `AppComponent`.
要显示 `HeroesComponent` 你必须把它加到壳组件 `AppComponent` 的模板中。
Remember that `app-heroes` is the [element selector](#selector) for the `HeroesComponent`.
So add an `<app-heroes>` element to the `AppComponent` template file, just below the title.
别忘了,`app-heroes` 就是 `HeroesComponent` 的 [元素选择器](#selector)。
所以,只要把 `<app-heroes>` 元素添加到 `AppComponent` 的模板文件中就可以了,就放在标题下方。
<code-example path="toh-pt1/src/app/app.component.html" title="src/app/app.component.html" linenums="false">
</code-example>
@ -88,24 +127,34 @@ So add an `<app-heroes>` element to the `AppComponent` template file, just below
Assuming that the CLI `ng serve` command is still running,
the browser should refresh and display both the application title and the hero name.
如果 CLI 的 `ng serve` 命令仍在运行,浏览器就会自动刷新,并且同时显示出应用的标题和英雄的名字。
## Create a Hero class
## 创建 `Hero`
A real hero is more than a name.
真实的英雄当然不止一个名字。
Create a `Hero` class in its own file in the `src/app` folder.
Give it `id` and `name` properties.
`src/app` 文件夹中为 `Hero` 类创建一个文件,并添加 `id``name` 属性。
<code-example path="toh-pt1/src/app/hero.ts" title="src/app/hero.ts" linenums="false">
</code-example>
Return to the `HeroesComponent` class and import the `Hero` class.
回到 `HeroesComponent` 类,并且导入这个 `Hero` 类。
Refactor the component's `hero` property to be of type `Hero`.
Initialize it with an `id` of `1` and the name `Windstorm`.
现在,有了一个`Hero`类,我们把组件`hero`属性的类型换成`Hero`
然后以`1`为 id、以 “Windstorm” 为名字初始化它。
把组件的 `hero` 属性的类型重构为 `Hero`
然后以`1`为 `id`、以 “Windstorm” 为名字初始化它。
The revised `HeroesComponent` class file should look like this:
@ -118,11 +167,17 @@ The revised `HeroesComponent` class file should look like this:
The page no longer displays properly because you changed the hero from a string to an object.
页面显示变得不正常了,因为你刚刚把 `hero` 从字符串改成了对象。
## Show the hero object
## 显示 `hero` 对象
Update the binding in the template to announce the hero's name
and show both `id` and `name` in a details layout like this:
修改模板中的绑定,以显示英雄的名字,并在详情中显示 `id``name`,就像这样:
<code-example
path="toh-pt1/src/app/heroes/heroes.component.1.html"
region="show-hero-2"
@ -136,8 +191,12 @@ The browser refreshes and display's the hero's information.
## Format with the _UppercasePipe_
## 使用 `UppercasePipe` 进行格式化
Modify the `hero.name` binding like this.
`hero.name` 的绑定修改成这样:
<code-example
path="toh-pt1/src/app/heroes/heroes.component.html"
region="pipe">
@ -146,13 +205,20 @@ Modify the `hero.name` binding like this.
The browser refreshes and now the hero's name is displayed in capital letters.
浏览器刷新了。现在,英雄的名字显示成了大写字母。
The word `uppercase` in the interpolation binding,
right after the pipe operator ( | ),
activates the built-in `UppercasePipe`.
绑定表达式中的 `uppercase` 位于管道操作符( | )的右边,用来调用内置管道 `UppercasePipe`
[Pipes](guide/pipes) are a good way to format strings, currency amounts, dates and other display data.
Angular ships with several built-in pipes and you can create your own.
[管道](guide/pipes) 是格式化字符串、金额、日期和其它显示数据的好办法。
Angular 发布了一些内置管道,而且你还可以创建自己的管道。
## Edit the hero
## 编辑英雄名字
@ -215,26 +281,45 @@ Can't bind to 'ngModel' since it isn't a known property of 'input'.
Although `ngModel` is a valid Angular directive, it isn't available by default.
虽然 `ngModel` 是一个有效的 Angular 指令,不过它在默认情况下是不可用的。
It belongs to the optional `FormsModule` and you must _opt-in_ to using it.
它属于一个可选模块`FormsModule`,你必须自行添加此模块才能使用该指令。
## _AppModule_
Angular needs to know how the pieces of your application fit together
and what other files and libraries the app requires.
This information is called _metadata_
Angular 需要知道如何把应用程序的各个部分组合到一起,以及该应用需要哪些其它文件和库。
这些信息被称为*元数据metadata*。
Some of the metadata is in the `@Component` decorators that you added to your component classes.
Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators.
有些元数据位于 `@Component` 装饰器中,你会把它加到组件类上。
另一些关键性的元数据位于 [`@NgModule`](guide/ngmodules) 装饰器中。
The most important `@NgModule`decorator annotates the top-level **AppModule** class.
最重要的 `@NgModule` 装饰器位于顶级类 **AppModule** 上。
The Angular CLI generated an `AppModule` class in `src/app/app.module.ts` when it created the project.
This is where you _opt-in_ to the `FormsModule`.
Angular CLI 在创建项目的时候就在 `src/app/app.module.ts` 中生成了一个 `AppModule` 类。
这里也就是你要添加 `FormsModule` 的地方。
### Import _FormsModule_
### 导入 `FormsModule`
Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the `@angular/forms` library.
打开 `AppModule` (`app.module.ts`) 并从 `@angular/forms` 库中导入 `FormsModule` 符号。
<code-example path="toh-pt1/src/app/app.module.ts" title="app.module.ts (FormsModule symbol import)"
region="formsmodule-js-import">
@ -242,6 +327,8 @@ Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the
Then add `FormsModule` to the `@NgModule` metadata's `imports` array, which contains a list of external modules that the app needs.
然后把 `FormsModule` 添加到 `@NgModule` 元数据的 `imports` 数组中,这里是该应用所需外部模块的列表。
<code-example path="toh-pt1/src/app/app.module.ts" title="app.module.ts ( @NgModule imports)"
region="ng-imports">
@ -249,35 +336,53 @@ region="ng-imports">
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>`中。
刷新浏览器,应用又能正常工作了。你可以编辑英雄的名字,并且会看到这个改动立刻体现在这个输入框上方的`<h2>`中。
### Declare _HeroesComponent_
### 声明 `HeroesComponent`
Every component must be declared in _exactly one_ [NgModule](guide/ngmodules).
每个组件都必须声明在(且只能声明在)一个 [NgModule](guide/ngmodules) 中。
_You_ didn't declare the `HeroesComponent`.
So why did the application work?
*你*没有声明过 `HeroesComponent`,可为什么本应用却正常呢?
It worked because the Angular CLI declared `HeroesComponent` in the `AppModule` when it generated that component.
这是因为 Angular CLI 在生成 `HeroesComponent` 组件的时候就自动把它加到了 `AppModule` 中。
Open `src/app/app.module.ts` and find `HeroesComponent` imported near the top.
打开 `src/app/app.module.ts` 你就会发现 `HeroesComponent` 已经在顶部导入过了。
<code-example path="toh-pt1/src/app/app.module.ts" region="heroes-import" >
</code-example>
The `HeroesComponent` is declared in the `@NgModule.declarations` array.
`HeroesComponent` 也已经声明在了 `@NgModule.declarations` 数组中。
<code-example path="toh-pt1/src/app/app.module.ts" region="declarations">
</code-example>
Note that `AppModule` declares both application components, `AppComponent` and `HeroesComponent`.
注意 `AppModule` 声明了应用中的所有组件,`AppComponent` 和 `HeroesComponent`
## Final code review
## 查看最终代码
Your app should look like this <live-example></live-example>. Here are the code files discussed on this page.
应用跑起来应该是这样的:<live-example></live-example>。本页中所提及的代码如下:
<code-tabs>
<code-pane title="src/app/heroes/heroes.component.ts" path="toh-pt1/src/app/heroes/heroes.component.ts">
@ -308,15 +413,29 @@ Your app should look like this <live-example></live-example>. Here are the code
* You used the CLI to create a second `HeroesComponent`.
你使用 CLI 创建了第二个组件 `HeroesComponent`
* You displayed the `HeroesComponent` by adding it to the `AppComponent` shell.
你把 `HeroesComponent` 添加到了壳组件 `AppComponent` 中,以便显示它。
* You applied the `UppercasePipe` to format the name.
你使用 `UppercasePipe` 来格式化英雄的名字。
* You used two-way data binding with the `ngModel` directive.
你用 `ngModel` 指令实现了双向数据绑定。
* You learned about the `AppModule`.
你知道了 `AppModule`
* You imported the `FormsModule` in the `AppModule` so that Angular would recognize and apply the `ngModel` directive.
你把 `FormsModule` 导入了 `AppModule`,以便 Angular 能识别并应用 `ngModel` 指令。
* You learned the importance of declaring components in the `AppModule`
and appreciated that the CLI declared it for you.
你知道了把组件声明到 `AppModule` 是很重要的,并认识到 CLI 会自动帮你声明它。

View File

@ -1,22 +1,35 @@
# Display a Heroes List
# 显示英雄列表
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.
我们需要管理多个英雄。我们将扩展《英雄指南》应用,让它显示一个英雄列表,
允许用户选择一个英雄,查看该英雄的详细信息。
本页中,你将扩展《英雄指南》应用,让它显示一个英雄列表,
允许用户选择一个英雄,查看该英雄的详细信息。
## Create mock heroes
## 创建模拟mock的英雄数据
You'll need some heroes to display.
你需要一些英雄数据以供显示。
Eventually you'll get them from a remote data server.
For now, you'll create some _mock heroes_ and pretend they came from the server.
最终,你会从远端的数据服务器获取它。
不过目前,你要先创建一些*模拟的英雄数据*,并假装它们是从服务器上取到的。
Create a file called `mock-heroes.ts` in the `src/app/` folder.
Define a `HEROES` constant as an array of ten heroes and export it.
The file should look like this.
`src/app/` 文件夹中创建一个名叫 `mock-heroes.ts` 的文件。
定义一个包含十个英雄的常量数组 `HEROES`,并导出它。
该文件是这样的。
<code-example path="toh-pt2/src/app/mock-heroes.ts" linenums="false"
title="src/app/mock-heroes.ts">
@ -24,42 +37,64 @@ title="src/app/mock-heroes.ts">
## Displaying heroes
## 显示我们的英雄
## 显示这些英雄
You're about to display the list of heroes at the top of the `HeroesComponent`.
你要在 `HeroesComponent` 的顶部显示这个英雄列表。
Open the `HeroesComponent` class file and import the mock `HEROES`.
打开 `HeroesComponent` 类文件,并导入模拟的 `HEROES`
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="import-heroes" title="src/app/heroes/heroes.component.ts (import HEROES)">
</code-example>
Add a `heroes` property to the class that exposes these heroes for binding.
往类中添加一个 `heroes` 属性,这样可以暴露出这些英雄,以供绑定。
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="heroes">
</code-example>
### List heroes with _*ngFor_
### 使用 `*ngFor` 列出这些英雄
Open the `HeroesComponent` template file and make the following changes:
打开 `HeroesComponent` 的模板文件,并做如下修改:
* Add an `<h2>` at the top,
在顶部添加 `<h2>`
* Below it add an HTML unordered list (`<ul>`)
然后添加表示无序列表的 HTML 元素(`<ul>`
* Insert an `<li>` within the `<ul>` that displays properties of a `hero`.
`<ul>` 中插入一个 `<li>` 元素,以显示单个 `hero` 的属性。
* Sprinkle some CSS classes for styling (you'll add the CSS styles shortly).
点缀上一些 CSS 类(稍后你还会添加更多 CSS 样式)。
Make it look like this:
做完之后应该是这样的:
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="list" title="heroes.component.html (heroes template)" linenums="false">
</code-example>
Now change the `<li>` to this:
现在,把 `<li>` 修改成这样:
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="li">
</code-example>
@ -67,48 +102,78 @@ Now change the `<li>` to this:
The [`*ngFor`](guide/template-syntax#ngFor) is Angular's _repeater_ directive.
It repeats the host element for each element in a list.
[`*ngFor`](guide/template-syntax#ngFor) 是一个 Angular 的复写器repeater指令。
它会为列表中的每项数据复写它的宿主元素。
In this example
在这个例子中
* `<li>` is the host element
`<li>` 就是 `*ngFor` 的宿主元素
* `heroes` is the list from the `HeroesComponent` class.
`heroes` 就是来自 `HeroesComponent` 类的列表。
* `hero` holds the current hero object for each iteration through the list.
当依次遍历这个列表时,`hero` 会为每个迭代保存当前的英雄对象。
<div class="alert is-important">
Don't forget the asterisk (*) in front of `ngFor`. It's a critical part of the syntax.
不要忘了 `ngFor` 前面的星号(`*`),它是该语法中的关键部分。
</div>
After the browser refreshes, the list of heroes appears.
浏览器刷新之后,英雄列表出现了。
{@a styles}
### Style the heroes
### 给我们的英雄们“美容”
### 给英雄们“美容”
The heroes list should be attractive and should respond visually when users
hover over and select a hero from the list.
英雄列表应该富有吸引力,并且当用户把鼠标移到某个英雄上和从列表中选中某个英雄时,应该给出视觉反馈。
In the [first tutorial](tutorial/toh-pt0#app-wide-styles), you set the basic styles for the entire application in `styles.css`.
That stylesheet didn't include styles for this list of heroes.
在[教程的第一章](tutorial/toh-pt0#app-wide-styles),你曾在 `styles.css` 中为整个应用设置了一些基础的样式。
但那个样式表并不包含英雄列表所需的样式。
You could add more styles to `styles.css` and keep growing that stylesheet as you add components.
固然,你可以把更多样式加入到 `styles.css`,并且放任它随着你添加更多组件而不断膨胀。
You may prefer instead to define private styles for a specific component and keep everything a component needs&mdash; the code, the HTML,
and the CSS &mdash;together in one place.
但还有更好的方式。你可以定义属于特定组件的私有样式并且让组件所需的一切代码、HTML 和 CSS都放在一起。
This approach makes it easier to re-use the component somewhere else
and deliver the component's intended appearance even if the global styles are different.
这种方式让你在其它地方复用该组件更加容易,并且即使全局样式和这里不一样,组件也仍然具有期望的外观。
You define private styles either inline in the `@Component.styles` array or
as stylesheet file(s) identified in the `@Component.styleUrls` array.
你可以用多种方式定义私有样式,或者内联在 `@Component.styles` 数组中,或者在 `@Component.styleUrls` 所指出的样式表文件中。
When the CLI generated the `HeroesComponent`, it created an empty `heroes.component.css` stylesheet for the `HeroesComponent`
and pointed to it in `@Component.styleUrls` like this.
当 CLI 生成 `HeroesComponent` 时,它也同时为 `HeroesComponent` 创建了空白的 `heroes.component.css` 样式表文件,并且让 `@Component.styleUrls` 指向它,就像这样:
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="metadata"
title="src/app/heroes/heroes.component.ts (@Component)">
@ -117,23 +182,37 @@ and pointed to it in `@Component.styleUrls` like this.
Open the `heroes.component.css` file and paste in the private CSS styles for the `HeroesComponent`.
You'll find them in the [final code review](#final-code-review) at the bottom of this guide.
打开 `heroes.component.css` 文件,并且把 `HeroesComponent` 的私有 CSS 样式粘贴进去。
你可以在本指南底部的[查看最终代码](#final-code-review)中找到它们。
<div class="alert is-important">
Styles and stylesheets identified in `@Component` metadata are scoped to that specific component.
The `heroes.component.css` styles apply only to the `HeroesComponent` and don't affect the outer HTML or the HTML in any other component.
`@Component` 元数据中指定的样式和样式表都是局限于该组件的。
`heroes.component.css` 中的样式只会作用于 `HeroesComponent`,既不会影响到组件外的 HTML也不会影响到其它组件中的 HTML。
</div>
## Master/Detail
## 主从结构
When the user clicks a hero in the **master** list,
the component should display the selected hero's **details** at the bottom of the page.
当用户在**主**列表中点击一个英雄时,该组件应该在页面底部显示所选英雄的**详情**。
In this section, you'll listen for the hero item click event
and update the hero detail.
在本节,你将监听英雄条目的点击事件,并更新英雄的详情。
### Add a click event binding
### 添加 `click` 事件绑定
Add a click event binding to the `<li>` like this:
我们再往`<li>`元素上插入一句点击事件的绑定代码:
@ -144,40 +223,66 @@ Add a click event binding to the `<li>` like this:
This is an example of Angular's [event binding](guide/template-syntax#event-binding) syntax.
这是 Angular [事件绑定](guide/template-syntax#event-binding) 语法的例子。
The parentheses around `click` tell Angular to listen for the `<li>` element's `click` event.
When the user clicks in the `<li>`, Angular executes the `onSelect(hero)` expression.
`click` 外面的圆括号会让 Angular 监听这个 `<li>` 元素的 `click` 事件。
当用户点击 `<li>`Angular 就会执行表达式 `onSelect(hero)`
`onSelect()` is a `HeroesComponent` method that you're about to write.
Angular calls it with the `hero` object displayed in the clicked `<li>`,
the same `hero` defined previously in the `*ngFor` expression.
`onSelect()``HeroesComponent` 上的一个方法,你很快就要写它。
Angular 会把所点击的 `<li>` 上的 `hero` 对象传给它,这个 `hero` 也就是以前在 `*ngFor` 表达式中定义的那个。
### Add the click event handler
### 添加 `click` 事件处理器
Rename the component's `hero` property to `selectedHero` but don't assign it.
There is no _selected hero_ when the application starts.
把该组件的 `hero` 属性改名为 `selectedHero`,但不要为它赋值。
因为应用刚刚启动时并没有*所选英雄*。
Add the following `onSelect()` method, which assigns the clicked hero from the template
to the component's `selectedHero`.
添加如下 `onSelect()` 方法,它会把模板中被点击的英雄赋值给组件的 `selectedHero` 属性。
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="on-select" title="src/app/heroes/heroes.component.ts (onSelect)" linenums="false">
</code-example>
### Update the details template
### 修改详情模板
The template still refers to the component's old `hero` property which no longer exists.
Rename `hero` to `selectedHero`.
该模板引用的仍然是老的 `hero` 属性,但它已经不存在了。
`hero` 改名为 `selectedHero`
<code-example path="toh-pt2/src/app/heroes/heroes.component.html" region="selectedHero-details" title="heroes.component.html (selected hero details)" linenums="false">
</code-example>
### Hide empty details with _*ngIf_
### 使用 `*ngIf` 隐藏空白的详情
After the browser refreshes, the application is broken.
刷新浏览器,应用挂了。
Open the browser developer tools and look in the console for an error message like this:
打开浏览器的开发者工具,它的控制台中显示出如下错误信息:
<code-example language="sh" class="code-shell">
HeroesComponent.html:3 ERROR TypeError: Cannot read property 'name' of undefined
@ -188,23 +293,42 @@ Now click one of the list items.
The app seems to be working again.
The heroes appear in a list and details about the clicked hero appear at the bottom of the page.
现在,从列表中随便点击一个条目。
应用又正常了。
英雄们显示在列表中,并且所点英雄的详情也显示在了页面的下方。
#### What happened?
#### 怎么回事?
When the app starts, the `selectedHero` is `undefined` _by design_.
当应用启动时,`selectedHero` 是 `undefined`*设计如此*。
Binding expressions in the template that refer to properties of `selectedHero` &mdash; expressions like `{{selectedHero.name}}` &mdash; _must fail_ because there is no selected hero.
但模板中的绑定表达式引用了 `selectedHero` 的属性(表达式为`{{selectedHero.name}}`),这必然会失败,因为你还没选过英雄呢。
#### The fix
#### 修复
The component should only display the selected hero details if the `selectedHero` exists.
该组件应该只有当 `selectedHero` 存在时才显示所选英雄的详情。
Wrap the hero detail HTML in a `<div>`.
Add Angular's `*ngIf` directive to the `<div>` and set it to `selectedHero`.
把显示英雄详情的 HTML 包裹在一个`<div>`中。
并且为这个 div 添加 Angular 的 `*ngIf` 指令,把它的值设置为 `selectedHero`
<div class="alert is-important">
Don't forget the asterisk (*) in front of `ngIf`. It's a critical part of the syntax.
不要忘了 `ngIf` 前面的星号(`*`),它是该语法中的关键部分。
</div>
<code-example path="toh-pt2/src/app/heroes/heroes.component.html" region="ng-if" title="src/app/heroes/heroes.component.html (*ngIf)" linenums="false">
@ -215,21 +339,35 @@ After the browser refreshes, the list of names reappears.
The details area is blank.
Click a hero and its details appear.
浏览器刷新之后,英雄名字的列表又出现了。
详情部分仍然是空。
点击一个英雄,它的详情就出现了。
#### Why it works
#### 为什么改好了?
When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to worry about.
`selectedHero``undefined` 时,`ngIf` 从 DOM 中移除了英雄详情。因此也就不用担心 `selectedHero` 的绑定了。
When the user picks a hero, `selectedHero` has a value and
`ngIf` puts the hero detail into the DOM.
当用户选择一个英雄时,`selectedHero` 也就有了值,并且 `ngIf` 把英雄的详情放回到 DOM 中。
### Style the selected hero
### 给所选英雄添加样式
It's difficult to identify the _selected hero_ in the list when all `<li>` elements look alike.
所有的 `<li>` 元素看起来都是一样的,因此很难从列表中识别出*所选英雄*。
If the user clicks "Magneta", that hero should render with a distinctive but subtle background color like this:
如果用户点击了“Magneta”这个英雄应该用一个略有不同的背景色显示出来就像这样
<figure>
<img src='generated/images/guide/toh/heroes-list-selected.png' alt="Selected hero">
@ -239,19 +377,31 @@ If the user clicks "Magneta", that hero should render with a distinctive but sub
That _selected hero_ coloring is the work of the `.selected` CSS class in the [styles you added earlier](#styles).
You just have to apply the `.selected` class to the `<li>` when the user clicks it.
*所选英雄*的颜色来自于[你前面添加的样式](#styles)中的 CSS 类 `.selected`
所以你只要在用户点击一个 `<li>` 时把 `.selected` 类应用到该元素上就可以了。
The Angular [class binding](guide/template-syntax#class-binding) makes it easy to add and remove a CSS class conditionally.
Just add `[class.some-css-class]="some-condition"` to the element you want to style.
Angular 的 [CSS 类绑定](guide/template-syntax#class-binding)机制让根据条件添加或移除一个 CSS 类变得很容易。
只要把 `[class.some-css-class]="some-condition"` 添加到你要施加样式的元素上就可以了。
Add the following `[class.selected]` binding to the `<li>` in the `HeroesComponent` template:
`HeroesComponent` 模板中的 `<li>` 元素上添加 `[class.selected]` 绑定,代码如下:
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="class-selected" title="heroes.component.html (toggle the 'selected' CSS class)" linenums="false">
</code-example>
When the current row hero is the same as the `selectedHero`, Angular adds the `selected` CSS class. When the two heroes are different, Angular removes the class.
如果当前行的英雄和 `selectedHero` 相同Angular 就会添加 CSS 类 `selected`,否则就会移除它。
The finished `<li>` looks like this:
最终的 `<li>` 是这样的:
<code-example path="toh-pt2/src/app/heroes/heroes.component.html" region="li" title="heroes.component.html (list item hero)" linenums="false">
</code-example>
@ -260,12 +410,16 @@ The finished `<li>` looks like this:
## Final code review
## 查看最终代码
Your app should look like this <live-example></live-example>.
我们的应用现在变成了这样:<live-example></live-example>
Here are the code files discussed on this page, including the `HeroesComponent` styles.
下面是本页面中所提及的代码文件,包括 `HeroesComponent` 的样式。
<code-tabs>
<code-pane title="src/app/heroes/heroes.component.ts" path="toh-pt2/src/app/heroes/heroes.component.ts">
@ -285,10 +439,20 @@ Here are the code files discussed on this page, including the `HeroesComponent`
* The Tour of Heroes app displays a list of heroes in a Master/Detail view.
英雄指南应用在一个主从视图中显示了英雄列表。
* The user can select a hero and see that hero's details.
用户可以选择一个英雄,并查看该英雄的详情。
* You used `*ngFor` to display a list.
你使用 `*ngFor` 显示了一个列表。
* You used `*ngIf` to conditionally include or exclude a block of HTML.
你使用 `*ngIf` 来根据条件包含或排除了一段 HTML。
* You can toggle a CSS style class with a `class` binding.
你可以用 `class` 绑定来切换 CSS 的样式类。

View File

@ -1,19 +1,35 @@
# Master/Detail Components
# 主从组件
At the moment, the `HeroesComponent` displays both the list of heroes and the selected hero's details.
此刻,`HeroesComponent` 同时显示了英雄列表和所选英雄的详情。
Keeping all features in one component as the application grows will not be maintainable.
You'll want to split up large components into smaller sub-components, each focused on a specific task or workflow.
In this page, you'll take the first step in that direction by moving the hero details into a separate, reusable `HeroDetailsComponent`.
把所有特性都放在同一个组件中,将会使应用“长大”后变得不可维护。
你要把大型组件拆分成小一点的子组件,每个子组件都要集中精力处理某个特定的任务或工作流。
In this page, you'll take the first step in that direction by moving the hero details into a separate, reusable `HeroDetailComponent`.
本页面中,你将迈出第一步 —— 把英雄详情移入一个独立的、可复用的 `HeroDetailComponent`
The `HeroesComponent` will only present the list of heroes.
The `HeroDetailsComponent` will present details of a selected hero.
The `HeroDetailComponent` will present details of a selected hero.
`HeroesComponent` 将仅仅用来表示英雄列表。
`HeroDetailComponent` 将用来表示所选英雄的详情。
## Make the `HeroDetailComponent`
## 制作 `HeroDetailComponent`
Use the Angular CLI to generate a new component named `hero-detail`.
使用 Angular CLI 生成一个名叫 `hero-detail` 的新组件。
<code-example language="sh" class="code-shell">
ng generate component hero-detail
@ -22,27 +38,44 @@ Use the Angular CLI to generate a new component named `hero-detail`.
The command scaffolds the `HeroDetailComponent` files and declares the component in `AppModule`.
该命令会生成 `HeroDetailComponent` 文件的脚手架,并把它声明在 `AppModule` 中。
### Write the template
### 编写模板
Cut the HTML for the hero detail from the bottom of the `HeroesComponent` template and paste it over the generated boilerplate in the `HeroDetailComponent` template.
`HeroesComponent` 模板的底部把表示英雄详情的 HTML 代码剪切粘贴到所生成的 `HeroDetailComponent` 模板中。
The pasted HTML refers to a `selectedHero`.
The new `HeroDetailComponent` can present _any_ hero, not just a selected hero.
So replace "selectedHero" with "hero" everywhere in the template.
所粘贴的 HTML 引用了 `selectedHero`
新的 `HeroDetailComponent` 可以展示*任意*英雄,而不仅仅所选的。因此还要把模板中的所有 `selectedHero` 替换为 `hero`
When you're done, the `HeroDetailComponent` template should look like this:
完工之后,`HeroDetailComponent` 的模板应该是这样的:
<code-example path="toh-pt3/src/app/hero-detail/hero-detail.component.html" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
</code-example>
### Add the `@Input()` hero property
### 添加 `@Input() hero` 属性
The `HeroDetailComponent` template binds to the component's `hero` property
which is of type `Hero`.
`HeroDetailComponent` 模板中绑定了组件中的 `hero` 属性,它的类型是 `Hero`
Open the `HeroDetailComponent` class file and import the `Hero` symbol.
打开 `HeroDetailComponent` 类文件,并导入 `Hero` 符号。
<code-example path="toh-pt3/src/app/hero-detail/hero-detail.component.ts"
region="import-hero" title="src/app/hero-detail/hero-detail.component.ts (import Hero)">
@ -53,18 +86,24 @@ The `hero` property
annotated with the `@Input()` decorator,
because the _external_ `HeroesComponent` [will bind to it](#heroes-component-template) like this.
`hero` 属性[必须是一个带有`@Input()`装饰器的输入属性](guide/template-syntax#inputs-outputs "Input and Output properties"),因为*外部的* `HeroesComponent` 组件[将会绑定到它](#heroes-component-template)。就像这样:
<code-example path="toh-pt3/src/app/heroes/heroes.component.html" region="hero-detail-binding">
</code-example>
Amend the `@angular/core` import statement to include the `Input` symbol.
修改 `@angular/core` 的导入语句,导入 `Input` 符号。
<code-example path="toh-pt3/src/app/hero-detail/hero-detail.component.ts" region="import-input" title="src/app/hero-detail/hero-detail.component.ts (import Input)" linenums="false">
</code-example>
Add a `hero` property, preceded by the `@Input()` decorator.
添加一个带有 `@Input()` 装饰器的 `hero` 属性。
<code-example path="toh-pt3/src/app/hero-detail/hero-detail.component.ts" region="input-hero" linenums="false">
</code-example>
@ -73,43 +112,73 @@ That's the only change you should make to the `HeroDetailComponent` class.
There are no more properties. There's no presentation logic.
This component simply receives a hero object through its `hero` property and displays it.
这就是你要对 `HeroDetailComponent` 类做的唯一一项修改。
没有其它属性,也没有展示逻辑。这个组件所做的只是通过 `hero` 属性接收一个英雄对象,并显示它。
## Show the `HeroDetailComponent`
## 显示 `HeroDetailComponent`
The `HeroesComponent` is still a master/detail view.
`HeroesComponent` 仍然是主从视图。
It used to display the hero details on its own, before you cut that portion of the template. Now it will delegate to the `HeroDetailComponent`.
在你从模板中剪切走代码之前,它自己负责显示英雄的详情。现在它要把这个职责委托给 `HeroDetailComponent` 了。
The two components will have a parent/child relationship.
The parent `HeroesComponent` will control the child `HeroDetailComponent`
by sending it a new hero to display whenever
the user selects a hero from the list.
这两个组件将会具有父子关系。
当用户从列表中选择了某个英雄时,父组件 `HeroesComponent` 将通过把要显示的新英雄发送给子组件 `HeroDetailComponent`,来控制子组件。
You won't change the `HeroesComponent` _class_ but you will change its _template_.
你不用修改 `HeroesComponent` *类*,但是要修改它的*模板*。
{@a heroes-component-template}
### Update the `HeroesComponent` template
### 修改 `HeroesComponent` 的模板
The `HeroDetailComponent` selector is `'app-hero-detail'`.
Add an `<app-hero-detail>` element near the bottom of the `HeroesComponent` template, where the hero detail view used to be.
`HeroDetailComponent` 的选择器是 `'app-hero-detail'`
`<app-hero-detail>` 添加到 `HeroesComponent` 模板的底部,以便把英雄详情的视图显示到那里。
Bind the `HeroesComponent.selectedHero` to the element's `hero` property like this.
`HeroesComponent.selectedHero` 绑定到钙元素的 `hero` 属性,就像这样:
<code-example path="toh-pt3/src/app/heroes/heroes.component.html" region="hero-detail-binding" title="heroes.component.html (HeroDetail binding)">
</code-example>
`[hero]="selectedHero"` is an Angular [property binding](guide/template-syntax#property-binding).
`[hero]="selectedHero"` 是 Angular 的[属性绑定](guide/template-syntax#property-binding)语法。
It's a _one way_ data binding from
the `selectedHero` property of the `HeroesComponent` to the `hero` property of the target element, which maps to the `hero` property of the `HeroDetailComponent`.
这是一种*单向*数据绑定。从 `HeroesComponent``selectedHero` 属性绑定到目标元素的 `hero` 属性,并映射到了 `HeroDetailComponent``hero` 属性。
Now when the user clicks a hero in the list, the `selectedHero` changes.
When the `selectedHero` changes, the _property binding_ updates `hero`
and the `HeroDetailComponent` displays the new hero.
现在,当用户在列表中点击某个英雄时,`selectedHero` 就改变了。
`selectedHero` 改变时,*属性绑定*会修改 `HeroDetailComponent``hero` 属性,`HeroDetailComponent` 就会显示这个新的英雄。
The revised `HeroesComponent` template should look like this:
修改后的 `HeroesComponent` 的模板是这样的:
<code-example path="toh-pt3/src/app/heroes/heroes.component.html"
title="heroes.component.html" linenums="false">
@ -117,6 +186,8 @@ The revised `HeroesComponent` template should look like this:
The browser refreshes and the app starts working again as it did before.
浏览器刷新,应用又像以前一样开始工作了。
## What changed?
## 有哪些变化?
@ -125,21 +196,38 @@ As [before](tutorial/toh-pt2), whenever a user clicks on a hero name,
the hero detail appears below the hero list.
Now the `HeroDetailComponent` is presenting those details instead of the `HeroesComponent`.
像[以前](tutorial/toh-pt2)一样,一旦用户点击了一个英雄的名字,该英雄的详情就显示在了英雄列表下方。
现在,`HeroDetailComponent` 负责显示那些详情,而不再是 `HeroesComponent`
Refactoring the original `HeroesComponent` into two components yields benefits, both now and in the future:
把原来的 `HeroesComponent` 重构成两个组件带来了一些优点,无论是现在还是未来:
1. You simplified the `HeroesComponent` by reducing its responsibilities.
你通过缩减 `HeroesComponent` 的职责简化了该组件。
1. You can evolve the `HeroDetailComponent` into a rich hero editor
without touching the parent `HeroesComponent`.
你可以把 `HeroDetailComponent` 改进成一个功能丰富的英雄编辑器,而不用改动父组件 `HeroesComponent`
1. You can evolve the `HeroesComponent` without touching the hero detail view.
你可以改进 `HeroesComponent`,而不用改动英雄详情视图。
1. You can re-use the `HeroDetailComponent` in the template of some future component.
将来你可以在其它组件的模板中重复使用 `HeroDetailComponent`
## Final code review
## 查看最终代码
Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
你的应用应该变成了这样 <live-example></live-example>。本页所提及的代码文件如下:
<code-tabs>
<code-pane title="src/app/hero-detail/hero-detail.component.ts" path="toh-pt3/src/app/hero-detail/hero-detail.component.ts">
@ -159,8 +247,15 @@ Here are the code files discussed on this page and your app should look like thi
* You created a separate, reusable `HeroDetailComponent`.
你创建了一个独立的、可复用的 `HeroDetailComponent` 组件。
* You used a [property binding](guide/template-syntax#property-binding) to give the parent `HeroesComponent` control over the child `HeroDetailComponent`.
你用[属性绑定](guide/template-syntax#property-binding)语法来让父组件 `HeroesComponent` 可以控制子组件 `HeroDetailComponent`
* You used the [`@Input` decorator](guide/template-syntax#inputs-outputs)
to make the `hero` property available for binding
by the external `HeroesComponent`.
你用 [`@Input` 装饰器](guide/template-syntax#inputs-outputs)来让 `hero` 属性可以在外部的 `HeroesComponent` 中绑定。

View File

@ -1,31 +1,56 @@
# Services
# 服务
The Tour of Heroes `HeroesComponent` is currently getting and displaying fake data.
英雄指南的 `HeroesComponent` 目前获取和显示的都是模拟数据。
After the refactoring in this tutorial, `HeroesComponent` will be lean and focused on supporting the view.
It will also be easier to unit-test with a mock service.
本节课的重构完成之后,`HeroesComponent` 变得更精简,并且聚焦于为它的视图提供支持。这也让它更容易使用模拟服务进行单元测试。
## Why services
## 为什么需要服务
Components shouldn't fetch or save data directly and they certainly shouldn't knowingly present fake data.
They should focus on presenting data and delegate data access to a service.
组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。
它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。
In this tutorial, you'll create a `HeroService` that all application classes can use to get heroes.
Instead of creating that service with `new`,
you'll rely on Angular [*dependency injection*](guide/dependency-injection)
to inject it into the `HeroesComponent` constructor.
本节课,你将创建一个 `HeroService`,应用中的所有类都可以使用它来获取英雄列表。
不要使用 `new` 来创建此服务,而要依靠 Angular 的[*依赖注入*](guide/dependency-injection)机制把它注入到 `HeroesComponent` 的构造函数中。
Services are a great way to share information among classes that _don't know each other_.
You'll create a `MessageService` and inject it in two places:
服务是在多个“互相不知道”的类之间共享信息的好办法。
你将创建一个 `MessageService`,并且把它注入到两个地方:
1. in `HeroService` which uses the service to send a message.
`HeroService` 中,它会使用该服务发送消息。
2. in `MessagesComponent` which displays that message.
`MessagesComponent` 中,它会显示其中的消息。
## Create the _HeroService_
## 创建 `HeroService`
Using the Angular CLI, create a service called `hero`.
使用 Angular CLI 创建一个名叫 `hero` 的服务。
<code-example language="sh" class="code-shell">
ng generate service hero
@ -35,6 +60,9 @@ Using the Angular CLI, create a service called `hero`.
The command generates skeleton `HeroService` class in `src/app/hero.service.ts`
The `HeroService` class should look like the below.
该命令会在 `src/app/hero.service.ts` 中生成 `HeroService` 类的骨架。
`HeroService` 类的代码如下:
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="new"
title="src/app/hero.service.ts (new service)" linenums="false">
@ -42,38 +70,61 @@ The `HeroService` class should look like the below.
### _@Injectable()_ services
### _@Injectable()_ 服务
Notice that the new service imports the Angular `Injectable` symbol and annotates
the class with the `@Injectable()` decorator.
注意,这个新的服务导入了 Angular 的 `Injectable` 符号,并且给这个服务类添加了 `@Injectable()` 装饰器。
The `@Injectable()` decorator tells Angular that this service _might_ itself
have injected dependencies.
It doesn't have dependencies now but [it will soon](#inject-message-service).
Whether it does or it doesn't, it's good practice to keep the decorator.
`@Injectable()` 装饰器告诉 Angular 这个服务本身*可能*拥有被注入的依赖。
目前它还没有依赖,但是[很快就会有了](#inject-message-service)。
无论它会不会有,总是给服务加上这个装饰器都是一种好的做法。
<div class="l-sub-section">
The Angular [style guidelines](guide/styleguide#style-07-04) strongly recommend keeping it
and the linter enforces this rule.
Angular 的[风格指南](guide/styleguide#style-07-04)强烈建议加上,而且 linter代码检查器 也会确保这条规则。
</div>
### Get hero data
### 获取英雄数据
The `HeroService` could get hero data from anywhere&mdash;a web service, local storage, or a mock data source.
`HeroService` 可以从任何地方获取数据Web 服务、本地存储LocalStorage或一个模拟的数据源。
Removing data access from components means you can change your mind about the implementation anytime, without touching any components.
They don't know how the service works.
从组件中移除数据访问逻辑,意味着将来任何时候你都可以改变目前的实现方式,而不用改动任何组件。
这些组件不需要了解该服务的内部实现。
The implementation in _this_ tutorial will continue to deliver _mock heroes_.
这节课中的实现仍然会提供*模拟的英雄列表*。
Import the `Hero` and `HEROES`.
导入 `Hero``HEROES`
<code-example path="toh-pt4/src/app/hero.service.ts" region="import-heroes">
</code-example>
Add a `getHeroes` method to return the _mock heroes_.
添加一个 `getHeroes` 方法,让它返回*模拟的英雄列表*。
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="getHeroes">
</code-example>
@ -82,19 +133,30 @@ Add a `getHeroes` method to return the _mock heroes_.
## Provide the `HeroService`
## 提供provide `HeroService`
You must _provide_ the `HeroService` in the _dependency injection system_
before Angular can _inject_ it into the `HeroesComponent`,
as you will do [below](#inject).
在要求 Angular 把`HeroService` 注入到 `HeroesComponent` 之前,你必须先把这个服务*提供给依赖注入系统*。[稍后](#inject)你就要这么做。
There are several ways to provide the `HeroService`:
in the `HeroesComponent`, in the `AppComponent`, in the `AppModule`.
Each option has pros and cons.
有很多途径可以提供 `HeroService`:在 `HeroesComponent` 中、在 `AppComponent` 中,或在 `AppModule` 中。
每个选项都各有优缺点。
This tutorial chooses to provide it in the `AppModule`.
这节课选择在 `AppModule` 中提供它。
That's such a popular choice that you could have told the CLI to provide it there automatically
by appending `--module=app`.
这是一个常用的选择,因此你可以通过 `--module=app` 选项让 CLI 自动把它提供给 `AppModule`
<code-example language="sh" class="code-shell">
ng generate service hero --module=app
@ -103,8 +165,12 @@ by appending `--module=app`.
Since you did not, you'll have to provide it yourself.
如果不这样做,你就要自行提供它。
Open the `AppModule` class, import the `HeroService`, and add it to the `@NgModule.providers` array.
打开 `AppModule` 类,导入 `HeroService`,并把它加入 `@NgModule.providers` 数组中。
<code-example path="toh-pt4/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (providers)" region="providers-heroservice">
</code-example>
@ -112,33 +178,50 @@ Open the `AppModule` class, import the `HeroService`, and add it to the `@NgModu
The `providers` array tells Angular to create a single, shared instance of `HeroService`
and inject into any class that asks for it.
`providers` 数组会告诉 Angular 创建 `HeroService` 的单一、共享的实例,并且把它注入到如何请求注入它的类中。
The `HeroService` is now ready to plug into the `HeroesComponent`.
现在 `HeroService` 已经准备好插入到 `HeroesComponent` 中了。
<div class="alert is-important">
This is a interim code sample that will allow you to provide and use the `HeroService`. At this point, the code will differ from the `HeroService` in the ["final code review"](#final-code-review).
这是一个过渡性的代码范例,它将会允许你提供并使用 `HeroService`。此刻的代码和[最终代码](#final-code-review)相差很大。
</div>
<div class="alert is-helpful">
Learn more about _providers_ in the [Providers](guide/providers) guide.
要进一步了解*提供商*,参见[服务提供商](guide/providers)一章。
</div>
## Update `HeroesComponent`
## 修改 `HeroesComponent`
Open the `HeroesComponent` class file.
打开 `HeroesComponent` 类文件。
Delete the `HEROES` import as you won't need that anymore.
Import the `HeroService` instead.
删除 `HEROES` 导入,我们以后不会再用它了。
转而导入 `HeroService`
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" title="src/app/heroes/heroes.component.ts (import HeroService)" region="hero-service-import">
</code-example>
Replace the definition of the `heroes` property with a simple declaration.
`heroes` 属性的定义改为一句简单的声明。
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="heroes">
</code-example>
@ -147,21 +230,33 @@ Replace the definition of the `heroes` property with a simple declaration.
### Inject the `HeroService`
### 注入 `HeroService`
Add a private `heroService` parameter of type `HeroService` to the constructor.
往构造函数中添加一个私有的 `heroService`,其类型为 `HeroService`
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="ctor">
</code-example>
The parameter simultaneously defines a private `heroService` property and identifies it as a `HeroService` injection site.
这个参数同时做了两件事1. 声明了一个私有 `heroService` 属性2. 把它标记为一个 `HeroService` 的注入点。
When Angular creates a `HeroesComponent`, the [Dependency Injection](guide/dependency-injection) system
sets the `heroService` parameter to the singleton instance of `HeroService`.
当 Angular 创建 `HeroesComponent` 时,[依赖注入](guide/dependency-injection)系统就会把这个 `heroService` 参数设置为 `HeroService` 的单例对象。
### Add _getHeroes()_
### 添加 _getHeroes()_
Create a function to retrieve the heroes from the service.
创建一个函数,以从服务中获取这些英雄数据。
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="getHeroes">
</code-example>
@ -170,31 +265,51 @@ Create a function to retrieve the heroes from the service.
### Call it in `ngOnInit`
### 在 `ngOnInit` 中调用它
While you could call `getHeroes()` in the constructor, that's not the best practice.
你固然可以在构造函数中调用 `getHeroes()`,但那不是最佳实践。
Reserve the constructor for simple initialization such as wiring constructor parameters to properties.
The constructor shouldn't _do anything_.
It certainly shouldn't call a function that makes HTTP requests to a remote server as a _real_ data service would.
让构造函数保持简单,只做初始化操作,比如把构造函数的参数赋值给属性。
构造函数不应该*做任何事*。
它肯定不能调用某个函数来向远端服务(比如真实的数据服务)发起 HTTP 请求。
Instead, call `getHeroes()` inside the [*ngOnInit lifecycle hook*](guide/lifecycle-hooks) and
let Angular call `ngOnInit` at an appropriate time _after_ constructing a `HeroesComponent` instance.
你应该改为在 [*ngOnInit 生命周期钩子*](guide/lifecycle-hooks)中调用 `getHeroes()`,并且等 Angular 构造出 `HeroesComponent` 的实例之后,找个恰当的时机调用 `ngOnInit`
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="ng-on-init">
</code-example>
### See it run
### 查看运行效果
After the browser refreshes, the app should run as before,
showing a list of heroes and a hero detail view when you click on a hero name.
刷新浏览器,该应用仍运行的一如既往。
显示英雄列表,并且当你点击某个英雄的名字时显示出英雄详情视图。
## Observable data
## 可观察Observable的数据
The `HeroService.getHeroes()` method has a _synchronous signature_,
which implies that the `HeroService` can fetch heroes synchronously.
The `HeroesComponent` consumes the `getHeroes()` result
as if heroes could be fetched synchronously.
`HeroService.getHeroes()` 的函数签名是*同步的*,它所隐含的假设是 `HeroService` 总是能同步获取英雄列表数据。
`HeroesComponent` 也同样假设能同步取到 `getHeroes()` 的结果。
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="get-heroes">
</code-example>
@ -204,27 +319,50 @@ You're getting away with it now because the service currently returns _mock hero
But soon the app will fetch heroes from a remote server,
which is an inherently _asynchronous_ operation.
这在真实的应用中几乎是不可能的。
现在能这么做,只是因为目前该服务返回的是*模拟数据*。
不过很快,该应用就要从远端服务器获取英雄数据了,而那天生就是*异步*操作。
The `HeroService` must wait for the server to respond,
`getHeroes()` cannot return immediately with hero data,
and the browser will not block while the service waits.
`HeroService` 必须等服务器给出相应,
`getHeroes()` 不能立即返回英雄数据,
浏览器也不会在该服务等待期间停止响应。
`HeroService.getHeroes()` must have an _asynchronous signature_ of some kind.
`HeroService.getHeroes()` 必须具有某种形式的*异步函数签名*。
It can take a callback. It could return a `Promise`. It could return an `Observable`.
它可以使用回调函数,可以返回 `Promise`(承诺),也可以返回 `Observable`(可观察对象)。
In this tutorial, `HeroService.getHeroes()` will return an `Observable`
in part because it will eventually use the Angular `HttpClient.get` method to fetch the heroes
and [`HttpClient.get()` returns an `Observable`](guide/http).
这节课,`HeroService.getHeroes()` 将会返回 `Observable`,因为它最终会使用 Angular 的 `HttpClient.get` 方法来获取英雄数据,而 [`HttpClient.get()` 会返回 `Observable`](guide/http)。
### Observable _HeroService_
### 可观察对象版本的 `HeroService`
`Observable` is one of the key classes in the [RxJS library](http://reactivex.io/rxjs/).
`Observable` 是 [RxJS 库](http://reactivex.io/rxjs/)中的一个关键类。
In a [later tutorial on HTTP](tutorial/toh-pt6), you'll learn that Angular's `HttpClient` methods return RxJS `Observable`s.
In this tutorial, you'll simulate getting data from the server with the RxJS `of()` function.
在[稍后的 HTTP 教程](tutorial/toh-pt6)中,你就会知道 Angular `HttpClient` 的方法会返回 RxJS 的 `Observable`
这节课,你将使用 RxJS 的 `of()` 函数来模拟从服务器返回数据。
Open the `HeroService` file and import the `Observable` and `of` symbols from RxJS.
打开 `HeroService` 文件,并从 RxJS 中导入 `Observable``of` 符号。
<code-example path="toh-pt4/src/app/hero.service.ts"
title="src/app/hero.service.ts (Observable imports)" region="import-observable">
@ -232,26 +370,41 @@ title="src/app/hero.service.ts (Observable imports)" region="import-observable">
Replace the `getHeroes` method with this one.
`getHeroes` 方法改成这样:
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes-1"></code-example>
`of(HEROES)` returns an `Observable<Hero[]>` that emits _a single value_, the array of mock heroes.
`of(HEROES)` 会返回一个 `Observable<Hero[]>`,它会发出单个值,这个值就是这些模拟英雄的数组。
<div class="l-sub-section">
In the [HTTP tutorial](tutorial/toh-pt6), you'll call `HttpClient.get<Hero[]>()` which also returns an `Observable<Hero[]>` that emits _a single value_, an array of heroes from the body of the HTTP response.
在 [HTTP 教程](tutorial/toh-pt6)中,你将会调用 `HttpClient.get<Hero[]>()` 它也同样返回一个 `Observable<Hero[]>`,它也会发出单个值,这个值就是来自 HTTP 响应体中的英雄数组。
</div>
### Subscribe in _HeroesComponent_
### 在 `HeroesComponent` 中订阅
The `HeroService.getHeroes` method used to return a `Hero[]`.
Now it returns an `Observable<Hero[]>`.
`HeroService.getHeroes` 方法用于返回一个 `Hero[]`
目前它返回的是 `Observable<Hero[]>`
You'll have to adjust to that difference in `HeroesComponent`.
你必须在 `HeroesComponent` 中也向本服务中的这种形式看齐。
Find the `getHeroes` method and replace it with the following code
(shown side-by-side with the previous version for comparison)
找到 `getHeroes` 方法,并且把它替换为如下代码(和前一个版本对比显示):
<code-tabs>
<code-pane title="heroes.component.ts (Observable)"
@ -266,36 +419,64 @@ Find the `getHeroes` method and replace it with the following code
`Observable.subscribe()` is the critical difference.
`Observable.subscribe()` 是关键的差异点。
The previous version assigns an array of heroes to the component's `heroes` property.
The assignment occurs _synchronously_, as if the server could return heroes instantly
or the browser could freeze the UI while it waited for the server's response.
上一个版本把英雄的数组赋值给了该组件的 `heroes` 属性。
这种赋值是*同步*的,这里包含的假设是服务器能立即返回英雄数组或者浏览器能在等待服务器响应时冻结界面。
That _won't work_ when the `HeroService` is actually making requests of a remote server.
`HeroService` 真的向远端服务器发起请求时,这种方式就行不通了。
The new version waits for the `Observable` to emit the array of heroes&mdash;
which could happen now or several minutes from now.
Then `subscribe` passes the emitted array to the callback,
which sets the component's `heroes` property.
新的版本等待 `Observable` 发出这个英雄数组,这可能立即发生,也可能会在几分钟之后。
然后,`subscribe` 函数把这个英雄数组传给这个回调函数,该函数把英雄数组赋值给组件的 `heroes` 属性。
This asynchronous approach _will work_ when
the `HeroService` requests heroes from the server.
使用这种异步方式,当 `HeroService` 从远端服务器获取英雄数据时,就*可以工作了*。
## Show messages
## 显示消息
In this section you will
在这一节,你将
* add a `MessagesComponent` that displays app messages at the bottom of the screen.
添加一个 `MessagesComponent`,它在屏幕的底部显示应用中的消息。
* create an injectable, app-wide `MessageService` for sending messages to be displayed
创建一个可注入的、全应用级别的 `MessageService`,用于发送要显示的消息。
* inject `MessageService` into the `HeroService`
`MessageService` 注入到 `HeroService` 中。
* display a message when `HeroService` fetches heroes successfully.
`HeroService` 成功获取了英雄数据时显示一条消息。
### Create _MessagesComponent_
### 创建 `MessagesComponent`
Use the CLI to create the `MessagesComponent`.
使用 CLI 创建 `MessagesComponent`
<code-example language="sh" class="code-shell">
ng generate component messages
@ -304,8 +485,12 @@ Use the CLI to create the `MessagesComponent`.
The CLI creates the component files in the `src/app/messages` folder and declare `MessagesComponent` in `AppModule`.
CLI 在 `src/app/messages` 中创建了组件文件,并且把 `MessagesComponent` 声明在了 `AppModule` 中。
Modify the `AppComponent` template to display the generated `MessagesComponent`
修改 `AppComponent` 的模板来显示所生成的 `MessagesComponent`
<code-example
title = "/src/app/app.component.html"
path="toh-pt4/src/app/app.component.html">
@ -314,11 +499,18 @@ Modify the `AppComponent` template to display the generated `MessagesComponent`
You should see the default paragraph from `MessagesComponent` at the bottom of the page.
你可以在页面的底部看到来自的 `MessagesComponent` 的默认内容。
### Create the _MessageService_
### 创建 `MessageService`
Use the CLI to create the `MessageService` in `src/app`.
The `--module=app` option tells the CLI to [_provide_ this service](#provide) in the `AppModule`,
使用 CLI 在 `src/app` 中创建 `MessageService`
`--module=app` 选项告诉 CLI 在 `AppModule` 中[提供这个服务](#provide)。
<code-example language="sh" class="code-shell">
ng generate service message --module=app
@ -327,6 +519,8 @@ The `--module=app` option tells the CLI to [_provide_ this service](#provide) i
Open `MessageService` and replace its contents with the following.
打开 `MessageService`,并把它的内容改成这样:
<code-example
title = "/src/app/message.service.ts"
path="toh-pt4/src/app/message.service.ts">
@ -335,12 +529,18 @@ Open `MessageService` and replace its contents with the following.
The service exposes its cache of `messages` and two methods: one to `add()` a message to the cache and another to `clear()` the cache.
该服务对外暴露了它的 `messages` 缓存,以及两个方法:`add()` 方法往缓存中添加一条消息,`clear()` 方法用于清空缓存。
{@a inject-message-service}
### Inject it into the `HeroService`
### 把它注入到 `HeroService`
Re-open the `HeroService` and import the `MessageService`.
重新打开 `HeroService`,并且导入 `MessageService`
<code-example
title = "/src/app/hero.service.ts (import MessageService)"
path="toh-pt4/src/app/hero.service.ts" region="import-message-service">
@ -351,6 +551,9 @@ Modify the constructor with a parameter that declares a private `messageService`
Angular will inject the singleton `MessageService` into that property
when it creates the `HeroService`.
修改这个构造函数,添加一个私有的 `messageService` 属性参数。
Angular 将会在创建 `HeroService` 时把 `MessageService` 的单例注入到这个属性中。
<code-example
path="toh-pt4/src/app/hero.service.ts" region="ctor">
@ -361,23 +564,37 @@ when it creates the `HeroService`.
This is a typical "*service-in-service*" scenario:
you inject the `MessageService` into the `HeroService` which is injected into the `HeroesComponent`.
这是一个典型的“服务中的服务”场景:
你把 `MessageService` 注入到了 `HeroService` 中,而 `HeroService` 又被注入到了 `HeroesComponent` 中。
</div>
### Send a message from `HeroService`
### 从 `HeroService` 中发送一条消息
Modify the `getHeroes` method to send a message when the heroes are fetched.
修改 `getHeroes` 方法,在获取到英雄数组时发送一条消息。
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes">
</code-example>
### Display the message from `HeroService`
### 从 `HeroService` 中显示消息
The `MessagesComponent` should display all messages,
including the message sent by the `HeroService` when it fetches heroes.
`MessagesComponent` 可以显示所有消息,
包括当 `HeroService` 获取到英雄数据时发送的那条。
Open `MessagesComponent` and import the `MessageService`.
打开 `MessagesComponent`,并且导入 `MessageService`
<code-example
title = "/src/app/messages/messages.component.ts (import MessageService)"
path="toh-pt4/src/app/messages/messages.component.ts" region="import-message-service">
@ -386,7 +603,10 @@ Open `MessagesComponent` and import the `MessageService`.
Modify the constructor with a parameter that declares a **public** `messageService` property.
Angular will inject the singleton `MessageService` into that property
when it creates the `HeroService`.
when it creates the `MessagesComponent`.
修改构造函数,添加一个 **public**`messageService` 属性。
Angular 将会在创建 `MessagesComponent` 的实例时 把 `MessageService` 的实例注入到这个属性中。
<code-example
path="toh-pt4/src/app/messages/messages.component.ts" region="ctor">
@ -395,16 +615,24 @@ when it creates the `HeroService`.
The `messageService` property **must be public** because you're about to bind to it in the template.
这个 `messageService` 属性必须是公开的,因为你将会在模板中绑定到它。
<div class="alert is-important">
Angular only binds to _public_ component properties.
Angular 只会绑定到组件的*公开*属性。
</div>
### Bind to the _MessageService_
### 绑定到 `MessageService`
Replace the CLI-generated `MessagesComponent` template with the following.
把 CLI 生成的 `MessagesComponent` 的模板改成这样:
<code-example
title = "src/app/messages/messages.component.html"
path="toh-pt4/src/app/messages/messages.component.html">
@ -413,26 +641,44 @@ Replace the CLI-generated `MessagesComponent` template with the following.
This template binds directly to the component's `messageService`.
这个模板直接绑定到了组件的 `messageService` 属性上。
* The `*ngIf` only displays the messages area if there are messages to show.
`*ngIf` 只有当在有消息时才会显示消息区。
* An `*ngFor` presents the list of messages in repeated `<div>` elements.
`*ngFor` 用来在一系列 `<div>` 元素中展示消息列表。
* An Angular [event binding](guide/template-syntax#event-binding) binds the button's click event
to `MessageService.clear()`.
Angular 的[事件绑定](guide/template-syntax#event-binding)把按钮的`click`事件绑定到了`MessageService.clear()`。
The messages will look better when you add the private CSS styles to `messages.component.css`
as listed in one of the ["final code review"](#final-code-review) tabs below.
当你把 [最终代码](#final-code-review) 某一页的内容添加到 `messages.component.css` 中时,这些消息会变得好看一些。
The browser refreshes and the page displays the list of heroes.
Scroll to the bottom to see the message from the `HeroService` in the message area.
Click the "clear" button and the message area disappears.
刷新浏览器,页面显示出了英雄列表。
滚动到底部,就会在消息区看到来自 `HeroService` 的消息。
点击“清空”按钮,消息区不见了。
{@a final-code-review}
## Final code review
## 查看最终代码
Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
你的应用应该变成了这样 <live-example></live-example>。本页所提及的代码文件如下:
<code-tabs>
<code-pane title="src/app/hero.service.ts"
@ -475,19 +721,37 @@ Here are the code files discussed on this page and your app should look like thi
* You refactored data access to the `HeroService` class.
你把数据访问逻辑重构到了 `HeroService` 类中。
* You _provided_ the `HeroService` in the root `AppModule` so that it can be injected anywhere.
你在根模块 `AppModule` 中提供了 `HeroService` 服务,以便在别处可以注入它。
* You used [Angular Dependency Injection](guide/dependency-injection) to inject it into a component.
你使用 [Angular 依赖注入](guide/dependency-injection)机制把它注入到了组件中。
* You gave the `HeroService` _get data_ method an asynchronous signature.
你给 `HeroService` 中获取数据的方法提供了一个异步的函数签名。
* You discovered `Observable` and the RxJS _Observable_ library.
你发现了 `Observable` 以及 RxJS 库。
* You used RxJS `of()` to return an _Observable_ of mock heroes (`Observable<Hero[]>`).
你使用 RxJS 的 `of()` 方法返回了一个模拟英雄数据的*可观察对象* (`Observable<Hero[]>`)。
* The component's `ngOnInit` lifecycle hook calls the `HeroService` method, not the constructor.
在组件的 `ngOnInit` 生命周期钩子中调用 `HeroService` 方法,而不是构造函数中。
* You created a `MessageService` for loosely-coupled communication between classes.
你创建了一个 `MessageService`,以便在类之间实现松耦合通讯。
* The `HeroService` injected into a component is created with another injected service,
`MessageService`.
`HeroService` 连同注入到它的服务 `MessageService` 一起,注入到了组件中。