diff --git a/aio/content/navigation.json b/aio/content/navigation.json index cf1f2ea4d4..bb40ea706c 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -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" + } ] } diff --git a/aio/content/tutorial/toh-pt0.md b/aio/content/tutorial/toh-pt0.md index 78c21556aa..32467727b1 100644 --- a/aio/content/tutorial/toh-pt0.md +++ b/aio/content/tutorial/toh-pt0.md @@ -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 . diff --git a/aio/content/tutorial/toh-pt1.md b/aio/content/tutorial/toh-pt1.md index 12cfbf62ce..9006b90def 100644 --- a/aio/content/tutorial/toh-pt1.md +++ b/aio/content/tutorial/toh-pt1.md @@ -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` 的新组件。 + 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` 的类文件如下: + @@ -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`— the component's CSS element selector + `selector`— 组件的选择器(CSS 元素选择器) + 1. `templateUrl`— the location of the component's template file. + `templateUrl`— 组件模板文件的位置。 + 1. `styleUrls`— the location of the component's private CSS styles. + `styleUrls`— 组件私有 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” 的英雄。 + ### 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` 属性的数据绑定。 + ## 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 `` element to the `AppComponent` template file, just below the title. +别忘了,`app-heroes` 就是 `HeroesComponent` 的 [元素选择器](#selector)。 +所以,只要把 `` 元素添加到 `AppComponent` 的模板文件中就可以了,就放在标题下方。 + @@ -88,24 +127,34 @@ So add an `` 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` 属性。 + 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`,就像这样: + @@ -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` 符号。 + @@ -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` 数组中,这里是该应用所需外部模块的列表。 + @@ -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 `

` above the textbox. -浏览器刷新。又见到我们的英雄了。我们可以编辑英雄的名字,也能看到这个改动立刻体现在`

`中。 +刷新浏览器,应用又能正常工作了。你可以编辑英雄的名字,并且会看到这个改动立刻体现在这个输入框上方的`

`中。 ### 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` 已经在顶部导入过了。 + The `HeroesComponent` is declared in the `@NgModule.declarations` array. +`HeroesComponent` 也已经声明在了 `@NgModule.declarations` 数组中。 + Note that `AppModule` declares both application components, `AppComponent` and `HeroesComponent`. +注意 `AppModule` 声明了应用中的所有组件,`AppComponent` 和 `HeroesComponent`。 + ## Final code review +## 查看最终代码 + Your app should look like this . Here are the code files discussed on this page. +应用跑起来应该是这样的:。本页中所提及的代码如下: + @@ -308,15 +413,29 @@ Your app should look like this . 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 会自动帮你声明它。 diff --git a/aio/content/tutorial/toh-pt2.md b/aio/content/tutorial/toh-pt2.md index ee999bec5a..31eab672d8 100644 --- a/aio/content/tutorial/toh-pt2.md +++ b/aio/content/tutorial/toh-pt2.md @@ -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`,并导出它。 +该文件是这样的。 + @@ -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`。 + Add a `heroes` property to the class that exposes these heroes for binding. +往类中添加一个 `heroes` 属性,这样可以暴露出这些英雄,以供绑定。 + ### List heroes with _*ngFor_ +### 使用 `*ngFor` 列出这些英雄 + Open the `HeroesComponent` template file and make the following changes: +打开 `HeroesComponent` 的模板文件,并做如下修改: + * Add an `

` at the top, + 在顶部添加 `

`, + * Below it add an HTML unordered list (`
    `) + 然后添加表示无序列表的 HTML 元素(`
      `) + * Insert an `
    • ` within the `
        ` that displays properties of a `hero`. + 在 `
          ` 中插入一个 `
        • ` 元素,以显示单个 `hero` 的属性。 + * Sprinkle some CSS classes for styling (you'll add the CSS styles shortly). + 点缀上一些 CSS 类(稍后你还会添加更多 CSS 样式)。 + Make it look like this: +做完之后应该是这样的: + Now change the `
        • ` to this: +现在,把 `
        • ` 修改成这样: + @@ -67,48 +102,78 @@ Now change the `
        • ` 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 +在这个例子中 + * `
        • ` is the host element + `
        • ` 就是 `*ngFor` 的宿主元素 + * `heroes` is the list from the `HeroesComponent` class. + `heroes` 就是来自 `HeroesComponent` 类的列表。 + * `hero` holds the current hero object for each iteration through the list. + 当依次遍历这个列表时,`hero` 会为每个迭代保存当前的英雄对象。 +
          Don't forget the asterisk (*) in front of `ngFor`. It's a critical part of the syntax. +不要忘了 `ngFor` 前面的星号(`*`),它是该语法中的关键部分。 +
          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— the code, the HTML, and the CSS —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` 指向它,就像这样: + @@ -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)中找到它们。 +
          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。 +
          ## 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 `
        • ` like this: 我们再往`
        • `元素上插入一句点击事件的绑定代码: @@ -144,40 +223,66 @@ Add a click event binding to the `
        • ` 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 `
        • ` element's `click` event. When the user clicks in the `
        • `, Angular executes the `onSelect(hero)` expression. +`click` 外面的圆括号会让 Angular 监听这个 `
        • ` 元素的 `click` 事件。 +当用户点击 `
        • ` 时,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 `
        • `, the same `hero` defined previously in the `*ngFor` expression. +`onSelect()` 是 `HeroesComponent` 上的一个方法,你很快就要写它。 +Angular 会把所点击的 `
        • ` 上的 `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` 属性。 + ### 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`。 + ### 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: +打开浏览器的开发者工具,它的控制台中显示出如下错误信息: + 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` — expressions like `{{selectedHero.name}}` — _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 `
          `. Add Angular's `*ngIf` directive to the `
          ` and set it to `selectedHero`. +把显示英雄详情的 HTML 包裹在一个`
          `中。 +并且为这个 div 添加 Angular 的 `*ngIf` 指令,把它的值设置为 `selectedHero`。 +
          Don't forget the asterisk (*) in front of `ngIf`. It's a critical part of the syntax. +不要忘了 `ngIf` 前面的星号(`*`),它是该语法中的关键部分。 +
          @@ -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 `
        • ` elements look alike. +所有的 `
        • ` 元素看起来都是一样的,因此很难从列表中识别出*所选英雄*。 + If the user clicks "Magneta", that hero should render with a distinctive but subtle background color like this: +如果用户点击了“Magneta”,这个英雄应该用一个略有不同的背景色显示出来,就像这样: +
          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 `
        • ` when the user clicks it. +*所选英雄*的颜色来自于[你前面添加的样式](#styles)中的 CSS 类 `.selected`。 +所以你只要在用户点击一个 `
        • ` 时把 `.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 `
        • ` in the `HeroesComponent` template: +在 `HeroesComponent` 模板中的 `
        • ` 元素上添加 `[class.selected]` 绑定,代码如下: + 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 `
        • ` looks like this: +最终的 `
        • ` 是这样的: + @@ -260,12 +410,16 @@ The finished `
        • ` looks like this: ## Final code review +## 查看最终代码 + Your app should look like this . 我们的应用现在变成了这样:。 Here are the code files discussed on this page, including the `HeroesComponent` styles. +下面是本页面中所提及的代码文件,包括 `HeroesComponent` 的样式。 + @@ -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 的样式类。 \ No newline at end of file diff --git a/aio/content/tutorial/toh-pt3.md b/aio/content/tutorial/toh-pt3.md index 572b68711c..e803579a5a 100644 --- a/aio/content/tutorial/toh-pt3.md +++ b/aio/content/tutorial/toh-pt3.md @@ -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` 的新组件。 + 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` 的模板应该是这样的: + ### 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` 符号。 + @@ -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)。就像这样: + Amend the `@angular/core` import statement to include the `Input` symbol. +修改 `@angular/core` 的导入语句,导入 `Input` 符号。 + Add a `hero` property, preceded by the `@Input()` decorator. +添加一个带有 `@Input()` 装饰器的 `hero` 属性。 + @@ -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 `` element near the bottom of the `HeroesComponent` template, where the hero detail view used to be. +`HeroDetailComponent` 的选择器是 `'app-hero-detail'`。 +把 `` 添加到 `HeroesComponent` 模板的底部,以便把英雄详情的视图显示到那里。 + Bind the `HeroesComponent.selectedHero` to the element's `hero` property like this. +把 `HeroesComponent.selectedHero` 绑定到钙元素的 `hero` 属性,就像这样: + `[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` 的模板是这样的: + @@ -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 . +你的应用应该变成了这样 。本页所提及的代码文件如下: + @@ -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` 中绑定。 + \ No newline at end of file diff --git a/aio/content/tutorial/toh-pt4.md b/aio/content/tutorial/toh-pt4.md index 6baa247b39..59e77c0127 100644 --- a/aio/content/tutorial/toh-pt4.md +++ b/aio/content/tutorial/toh-pt4.md @@ -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` 的服务。 + 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` 类的代码如下: + @@ -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)。 +无论它会不会有,总是给服务加上这个装饰器都是一种好的做法。 +
          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(代码检查器) 也会确保这条规则。 +
          ### Get hero data +### 获取英雄数据 + The `HeroService` could get hero data from anywhere—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`。 + Add a `getHeroes` method to return the _mock heroes_. +添加一个 `getHeroes` 方法,让它返回*模拟的英雄列表*。 + @@ -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`。 + 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` 数组中。 + @@ -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` 中了。 +
          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)相差很大。 +
          Learn more about _providers_ in the [Providers](guide/providers) guide. + 要进一步了解*提供商*,参见[服务提供商](guide/providers)一章。 +
          ## 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`。 + Replace the definition of the `heroes` property with a simple declaration. +把 `heroes` 属性的定义改为一句简单的声明。 + @@ -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`。 + 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. +创建一个函数,以从服务中获取这些英雄数据。 + @@ -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`。 + ### 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()` 的结果。 + @@ -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` 符号。 + @@ -232,26 +370,41 @@ title="src/app/hero.service.ts (Observable imports)" region="import-observable"> Replace the `getHeroes` method with this one. +把 `getHeroes` 方法改成这样: + `of(HEROES)` returns an `Observable` that emits _a single value_, the array of mock heroes. +`of(HEROES)` 会返回一个 `Observable`,它会发出单个值,这个值就是这些模拟英雄的数组。 +
          In the [HTTP tutorial](tutorial/toh-pt6), you'll call `HttpClient.get()` which also returns an `Observable` that emits _a single value_, an array of heroes from the body of the HTTP response. +在 [HTTP 教程](tutorial/toh-pt6)中,你将会调用 `HttpClient.get()` 它也同样返回一个 `Observable`,它也会发出单个值,这个值就是来自 HTTP 响应体中的英雄数组。 +
          ### Subscribe in _HeroesComponent_ +### 在 `HeroesComponent` 中订阅 + The `HeroService.getHeroes` method used to return a `Hero[]`. Now it returns an `Observable`. +`HeroService.getHeroes` 方法用于返回一个 `Hero[]`, +目前它返回的是 `Observable`。 + 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` 方法,并且把它替换为如下代码(和前一个版本对比显示): + 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`: + @@ -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)。 + 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`,并把它的内容改成这样: + @@ -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`。 + @@ -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` 的单例注入到这个属性中。 + @@ -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` 中。 + ### Send a message from `HeroService` +### 从 `HeroService` 中发送一条消息 + Modify the `getHeroes` method to send a message when the heroes are fetched. +修改 `getHeroes` 方法,在获取到英雄数组时发送一条消息。 + ### 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`。 + @@ -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` 的实例注入到这个属性中。 @@ -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` 属性必须是公开的,因为你将会在模板中绑定到它。 +
          Angular only binds to _public_ component properties. +Angular 只会绑定到组件的*公开*属性。 +
          ### Bind to the _MessageService_ +### 绑定到 `MessageService` + Replace the CLI-generated `MessagesComponent` template with the following. +把 CLI 生成的 `MessagesComponent` 的模板改成这样: + @@ -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 `
          ` elements. + `*ngFor` 用来在一系列 `
          ` 元素中展示消息列表。 + * 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 . +你的应用应该变成了这样 。本页所提及的代码文件如下: + `). + 你使用 RxJS 的 `of()` 方法返回了一个模拟英雄数据的*可观察对象* (`Observable`)。 + * 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` 一起,注入到了组件中。