713 lines
28 KiB
Markdown
Raw Permalink Normal View History

2017-11-06 19:02:18 +01:00
# Services
2018-03-08 17:07:55 +08:00
# 服务
2017-11-06 19:02:18 +01:00
The Tour of Heroes `HeroesComponent` is currently getting and displaying fake data.
2018-03-08 17:07:55 +08:00
英雄指南的 `HeroesComponent` 目前获取和显示的都是模拟数据。
2017-11-06 19:02:18 +01:00
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.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
本节课的重构完成之后,`HeroesComponent` 变得更精简,并且聚焦于为它的视图提供支持。这也让它更容易使用模拟服务进行单元测试。
2017-11-06 19:02:18 +01:00
## Why services
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
## 为什么需要服务
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。
它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
本节课,你将创建一个 `HeroService`,应用中的所有类都可以使用它来获取英雄列表。
不要使用 `new` 来创建此服务,而要依靠 Angular 的[*依赖注入*](guide/dependency-injection)机制把它注入到 `HeroesComponent` 的构造函数中。
2017-11-06 19:02:18 +01:00
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:
2018-03-08 17:07:55 +08:00
服务是在多个“互相不知道”的类之间共享信息的好办法。
你将创建一个 `MessageService`,并且把它注入到两个地方:
2017-11-06 19:02:18 +01:00
1. in `HeroService` which uses the service to send a message.
2018-03-08 17:07:55 +08:00
`HeroService` 中,它会使用该服务发送消息。
2018-03-03 21:06:01 +08:00
2. in `MessagesComponent` which displays that message.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
`MessagesComponent` 中,它会显示其中的消息。
2017-11-06 19:02:18 +01:00
## Create the _HeroService_
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
## 创建 `HeroService`
2017-11-06 19:02:18 +01:00
Using the Angular CLI, create a service called `hero`.
2018-03-08 17:07:55 +08:00
使用 Angular CLI 创建一个名叫 `hero` 的服务。
<code-example language="sh" class="code-shell">
2017-11-06 19:02:18 +01:00
ng generate service hero
</code-example>
The command generates skeleton `HeroService` class in `src/app/hero.service.ts`
2018-05-15 13:32:40 +08:00
The `HeroService` class should look like the following example.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
该命令会在 `src/app/hero.service.ts` 中生成 `HeroService` 类的骨架。
`HeroService` 类的代码如下:
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="new"
header="src/app/hero.service.ts (new service)" linenums="false">
2017-11-06 19:02:18 +01:00
</code-example>
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
### _@Injectable()_ services
2018-03-08 17:07:55 +08:00
### _@Injectable()_ 服务
2017-11-06 19:02:18 +01:00
Notice that the new service imports the Angular `Injectable` symbol and annotates
2018-05-15 13:32:40 +08:00
the class with the `@Injectable()` decorator. This marks the class as one that participates in the _dependency injection system_. The `HeroService` class is going to provide an injectable service, and it can also have its own injected dependencies.
It doesn't have any dependencies yet, but [it will soon](#inject-message-service).
2018-03-08 17:07:55 +08:00
注意,这个新的服务导入了 Angular 的 `Injectable` 符号,并且给这个服务类添加了 `@Injectable()` 装饰器。
2018-05-15 13:32:40 +08:00
它把这个类标记为*依赖注入系统*的参与者之一。`HeroService` 类将会提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖。
2018-03-08 17:07:55 +08:00
目前它还没有依赖,但是[很快就会有了](#inject-message-service)。
2018-05-15 13:32:40 +08:00
The `@Injectable()` decorator accepts a metadata object for the service, the same way the `@Component()` decorator did for your component classes.
2018-03-08 17:07:55 +08:00
2018-05-15 13:32:40 +08:00
`@Injectable()` 装饰器会接受该服务的元数据对象,就像 `@Component()` 对组件类的作用一样。
2017-11-06 19:02:18 +01:00
### Get hero data
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
### 获取英雄数据
2017-11-06 19:02:18 +01:00
The `HeroService` could get hero data from anywhere&mdash;a web service, local storage, or a mock data source.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
`HeroService` 可以从任何地方获取数据Web 服务、本地存储LocalStorage或一个模拟的数据源。
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
从组件中移除数据访问逻辑,意味着将来任何时候你都可以改变目前的实现方式,而不用改动任何组件。
这些组件不需要了解该服务的内部实现。
2017-11-06 19:02:18 +01:00
The implementation in _this_ tutorial will continue to deliver _mock heroes_.
2018-03-08 17:07:55 +08:00
这节课中的实现仍然会提供*模拟的英雄列表*。
2017-11-06 19:02:18 +01:00
Import the `Hero` and `HEROES`.
2018-03-08 17:07:55 +08:00
导入 `Hero``HEROES`
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/hero.service.ts" region="import-heroes">
</code-example>
2017-11-06 19:02:18 +01:00
Add a `getHeroes` method to return the _mock heroes_.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
添加一个 `getHeroes` 方法,让它返回*模拟的英雄列表*。
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="getHeroes">
</code-example>
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
{@a provide}
2018-03-03 21:06:01 +08:00
2017-11-06 19:02:18 +01:00
## Provide the `HeroService`
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
## 提供provide `HeroService`
2018-05-15 13:32:40 +08:00
You must make the `HeroService` available to the dependency injection system
2017-11-06 19:02:18 +01:00
before Angular can _inject_ it into the `HeroesComponent`,
2018-05-15 13:32:40 +08:00
as you will do [below](#inject). You do this by registering a _provider_. A provider is something that can create or deliver a service; in this case, it instantiates the `HeroService` class to provide the service.
2017-04-01 01:57:13 +02:00
在要求 Angular 把 `HeroService` 注入到 `HeroesComponent` 之前,你必须先把这个服务*提供给依赖注入系统*。[稍后](#inject)你就要这么做。
2018-05-15 13:32:40 +08:00
你可以通过注册*提供商*来做到这一点。提供商用来创建和交付服务,在这个例子中,它会对 `HeroService` 类进行实例化,以提供该服务。
2018-03-08 17:07:55 +08:00
2018-05-15 13:32:40 +08:00
Now, you need to make sure that the `HeroService` is registered as the provider of this service.
You are registering it with an _injector_, which is the object that is responsible for choosing and injecting the provider where it is required.
2018-05-15 13:32:40 +08:00
现在,你需要确保 `HeroService` 已经作为该服务的提供商进行过注册。
你要用一个*注入器*注册它。注入器就是一个对象,负责在需要时选取和注入该提供商。
2018-03-08 17:07:55 +08:00
2018-05-15 13:32:40 +08:00
By default, the Angular CLI command `ng generate service` registers a provider with the _root injector_ for your service by including provider metadata in the `@Injectable` decorator.
2017-04-01 01:57:13 +02:00
2019-01-16 15:01:56 +08:00
默认情况下Angular CLI 命令 `ng generate service` 会通过给 `@Injectable` 装饰器添加元数据的形式,用*根注入器*将你的服务注册成为提供商。
2018-03-08 17:07:55 +08:00
2018-05-15 13:32:40 +08:00
If you look at the `@Injectable()` statement right before the `HeroService` class definition, you can see that the `providedIn` metadata value is 'root':
2017-04-01 01:57:13 +02:00
2018-05-15 13:32:40 +08:00
如果你看看 `HeroService` 紧前面的 `@Injectable()` 语句定义,就会发现 `providedIn` 元数据的值是 'root'
2018-03-08 17:07:55 +08:00
2018-05-15 13:32:40 +08:00
```
2018-05-15 13:32:40 +08:00
@Injectable({
providedIn: 'root',
})
2018-05-15 13:32:40 +08:00
```
2018-03-08 17:07:55 +08:00
2018-05-15 13:32:40 +08:00
When you provide the service at the root level, Angular creates a single, shared instance of `HeroService` and injects into any class that asks for it.
Registering the provider in the `@Injectable` metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
当你在顶层提供该服务时Angular 就会为 `HeroService` 创建一个单一的、共享的实例,并把它注入到任何想要它的类上。
2018-12-25 15:01:37 +08:00
`@Injectable` 元数据中注册该提供商,还能允许 Angular 通过移除那些完全没有用过的服务来进行优化。
2018-05-15 13:32:40 +08:00
<div class="alert is-helpful">
To learn more about providers, see the [Providers section](guide/providers).
To learn more about injectors, see the [Dependency Injection guide](guide/dependency-injection).
2018-03-08 17:07:55 +08:00
Merge remote-tracking branch 'en/master' into aio # Conflicts: # aio/content/guide/ajs-quick-reference.md # aio/content/guide/animations.md # aio/content/guide/aot-compiler.md # aio/content/guide/architecture-components.md # aio/content/guide/architecture-modules.md # aio/content/guide/architecture-next-steps.md # aio/content/guide/architecture-services.md # aio/content/guide/architecture.md # aio/content/guide/attribute-directives.md # aio/content/guide/bootstrapping.md # aio/content/guide/browser-support.md # aio/content/guide/cheatsheet.md # aio/content/guide/comparing-observables.md # aio/content/guide/component-interaction.md # aio/content/guide/component-styles.md # aio/content/guide/dependency-injection-in-action.md # aio/content/guide/dependency-injection-pattern.md # aio/content/guide/dependency-injection.md # aio/content/guide/deployment.md # aio/content/guide/displaying-data.md # aio/content/guide/dynamic-component-loader.md # aio/content/guide/elements.md # aio/content/guide/feature-modules.md # aio/content/guide/form-validation.md # aio/content/guide/forms.md # aio/content/guide/glossary.md # aio/content/guide/hierarchical-dependency-injection.md # aio/content/guide/i18n.md # aio/content/guide/lazy-loading-ngmodules.md # aio/content/guide/lifecycle-hooks.md # aio/content/guide/ngmodule-faq.md # aio/content/guide/ngmodule-vs-jsmodule.md # aio/content/guide/ngmodules.md # aio/content/guide/npm-packages.md # aio/content/guide/observables-in-angular.md # aio/content/guide/observables.md # aio/content/guide/pipes.md # aio/content/guide/practical-observable-usage.md # aio/content/guide/providers.md # aio/content/guide/quickstart.md # aio/content/guide/reactive-forms.md # aio/content/guide/router.md # aio/content/guide/rx-library.md # aio/content/guide/security.md # aio/content/guide/service-worker-communications.md # aio/content/guide/service-worker-getting-started.md # aio/content/guide/service-worker-intro.md # aio/content/guide/setup-systemjs-anatomy.md # aio/content/guide/setup.md # aio/content/guide/singleton-services.md # aio/content/guide/structural-directives.md # aio/content/guide/styleguide.md # aio/content/guide/template-syntax.md # aio/content/guide/testing.md # aio/content/guide/typescript-configuration.md # aio/content/guide/universal.md # aio/content/guide/updating.md # aio/content/guide/upgrade.md # aio/content/guide/user-input.md # aio/content/guide/visual-studio-2015.md # aio/content/marketing/docs.md # aio/content/navigation.json # aio/content/tutorial/toh-pt0.md # aio/content/tutorial/toh-pt1.md # aio/content/tutorial/toh-pt2.md # aio/content/tutorial/toh-pt3.md # aio/content/tutorial/toh-pt4.md # aio/content/tutorial/toh-pt5.md # aio/content/tutorial/toh-pt6.md # aio/ngsw-manifest.json # aio/package.json # aio/src/app/custom-elements/api/api-list.component.html # aio/src/app/custom-elements/api/api-list.component.ts # aio/src/index.html # aio/tools/transforms/templates/api/base.template.html # aio/tools/transforms/templates/api/class.template.html # aio/tools/transforms/templates/api/directive.template.html # aio/tools/transforms/templates/api/enum.template.html # aio/tools/transforms/templates/api/includes/class-overview.html # aio/tools/transforms/templates/api/includes/deprecation.html # aio/tools/transforms/templates/api/includes/export-as.html # aio/tools/transforms/templates/api/includes/info-bar.html # aio/tools/transforms/templates/api/includes/interface-overview.html # aio/tools/transforms/templates/api/includes/selectors.html # aio/tools/transforms/templates/api/lib/directiveHelpers.html # aio/tools/transforms/templates/api/lib/githubLinks.html # aio/tools/transforms/templates/api/lib/memberHelpers.html # aio/tools/transforms/templates/api/package.template.html # aio/yarn.lock # packages/common/http/src/module.ts # packages/common/src/common_module.ts # packages/common/src/directives/ng_for_of.ts # packages/common/src/directives/ng_if.ts # packages/common/src/directives/ng_template_outlet.ts # packages/common/src/location/location.ts # packages/common/src/pipes/async_pipe.ts # packages/common/src/pipes/json_pipe.ts # packages/common/src/pipes/number_pipe.ts # packages/common/src/pipes/slice_pipe.ts # packages/core/src/change_detection/change_detector_ref.ts # packages/core/src/di/injectable.ts # packages/core/src/linker/template_ref.ts # packages/core/src/linker/view_container_ref.ts # packages/core/src/metadata/di.ts # packages/core/src/metadata/ng_module.ts # packages/core/src/render/api.ts # packages/forms/src/directives/form_interface.ts # packages/forms/src/directives/ng_form.ts # packages/forms/src/directives/ng_model.ts # packages/forms/src/directives/reactive_directives/form_control_name.ts # packages/forms/src/directives/select_control_value_accessor.ts # packages/forms/src/directives/validators.ts # packages/forms/src/form_providers.ts # packages/forms/src/model.ts # packages/forms/src/validators.ts # packages/router/src/config.ts # packages/router/src/router.ts # packages/router/src/router_module.ts # packages/router/src/router_state.ts
2018-10-21 17:43:29 +08:00
要了解关于提供商的更多知识,参见[提供商部分](guide/providers)。
要了解关于注入器的更多知识,参见[依赖注入指南](guide/dependency-injection)。
2018-05-15 13:32:40 +08:00
</div>
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
The `HeroService` is now ready to plug into the `HeroesComponent`.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
现在 `HeroService` 已经准备好插入到 `HeroesComponent` 中了。
<div class="alert is-important">
This is an 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).
2018-03-08 17:07:55 +08:00
这是一个过渡性的代码范例,它将会允许你提供并使用 `HeroService`。此刻的代码和[最终代码](#final-code-review)相差很大。
</div>
2017-11-06 19:02:18 +01:00
## Update `HeroesComponent`
2018-03-08 17:07:55 +08:00
## 修改 `HeroesComponent`
2017-11-06 19:02:18 +01:00
Open the `HeroesComponent` class file.
2018-03-08 17:07:55 +08:00
打开 `HeroesComponent` 类文件。
2018-05-15 13:32:40 +08:00
Delete the `HEROES` import, because you won't need that anymore.
2017-11-06 19:02:18 +01:00
Import the `HeroService` instead.
2018-05-15 13:32:40 +08:00
删除 `HEROES` 的导入语句,因为你以后不会再用它了。
2018-03-08 17:07:55 +08:00
转而导入 `HeroService`
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" header="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.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
`heroes` 属性的定义改为一句简单的声明。
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="heroes">
</code-example>
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
{@a inject}
2017-11-06 19:02:18 +01:00
### Inject the `HeroService`
2018-03-08 17:07:55 +08:00
### 注入 `HeroService`
2017-11-06 19:02:18 +01:00
Add a private `heroService` parameter of type `HeroService` to the constructor.
2018-03-08 17:07:55 +08:00
往构造函数中添加一个私有的 `heroService`,其类型为 `HeroService`
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="ctor">
</code-example>
2017-11-06 19:02:18 +01:00
The parameter simultaneously defines a private `heroService` property and identifies it as a `HeroService` injection site.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
这个参数同时做了两件事1. 声明了一个私有 `heroService` 属性2. 把它标记为一个 `HeroService` 的注入点。
2017-11-06 19:02:18 +01:00
When Angular creates a `HeroesComponent`, the [Dependency Injection](guide/dependency-injection) system
sets the `heroService` parameter to the singleton instance of `HeroService`.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
当 Angular 创建 `HeroesComponent` 时,[依赖注入](guide/dependency-injection)系统就会把这个 `heroService` 参数设置为 `HeroService` 的单例对象。
2017-11-06 19:02:18 +01:00
### Add _getHeroes()_
2018-03-08 17:07:55 +08:00
### 添加 _getHeroes()_
2017-11-06 19:02:18 +01:00
Create a function to retrieve the heroes from the service.
2018-03-08 17:07:55 +08:00
创建一个函数,以从服务中获取这些英雄数据。
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="getHeroes">
</code-example>
2017-11-06 19:02:18 +01:00
{@a oninit}
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
### Call it in `ngOnInit`
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
### 在 `ngOnInit` 中调用它
2017-11-06 19:02:18 +01:00
While you could call `getHeroes()` in the constructor, that's not the best practice.
2018-03-08 17:07:55 +08:00
你固然可以在构造函数中调用 `getHeroes()`,但那不是最佳实践。
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
让构造函数保持简单,只做初始化操作,比如把构造函数的参数赋值给属性。
构造函数不应该*做任何事*。
2019-01-16 15:01:56 +08:00
它当然不应该调用某个函数来向远端服务(比如真实的数据服务)发起 HTTP 请求。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
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 生命周期钩子中调用 getHeroes(),之后交由 Angular 处理,它会在构造出 HeroesComponent 的实例之后的某个合适的时机调用 ngOnInit。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="ng-on-init">
</code-example>
2017-11-06 19:02:18 +01:00
### See it run
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
### 查看运行效果
2017-11-06 19:02:18 +01:00
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.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
刷新浏览器,该应用仍运行的一如既往。
显示英雄列表,并且当你点击某个英雄的名字时显示出英雄详情视图。
2017-11-06 19:02:18 +01:00
## Observable data
2018-03-08 17:07:55 +08:00
## 可观察Observable的数据
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
`HeroService.getHeroes()` 的函数签名是*同步的*,它所隐含的假设是 `HeroService` 总是能同步获取英雄列表数据。
`HeroesComponent` 也同样假设能同步取到 `getHeroes()` 的结果。
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="get-heroes">
</code-example>
2017-11-06 19:02:18 +01:00
This will not work in a real app.
You're getting away with it now because the service currently returns _mock heroes_.
But soon the app will fetch heroes from a remote server,
which is an inherently _asynchronous_ operation.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
这在真实的应用中几乎是不可能的。
现在能这么做,只是因为目前该服务返回的是*模拟数据*。
不过很快,该应用就要从远端服务器获取英雄数据了,而那天生就是*异步*操作。
2017-11-06 19:02:18 +01:00
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.
2017-04-01 01:57:13 +02:00
2018-08-23 21:51:48 +08:00
`HeroService` 必须等服务器给出响应,
2018-03-08 17:07:55 +08:00
`getHeroes()` 不能立即返回英雄数据,
浏览器也不会在该服务等待期间停止响应。
2017-11-06 19:02:18 +01:00
`HeroService.getHeroes()` must have an _asynchronous signature_ of some kind.
2018-03-08 17:07:55 +08:00
`HeroService.getHeroes()` 必须具有某种形式的*异步函数签名*。
2017-11-06 19:02:18 +01:00
It can take a callback. It could return a `Promise`. It could return an `Observable`.
2018-03-08 17:07:55 +08:00
它可以使用回调函数,可以返回 `Promise`(承诺),也可以返回 `Observable`(可观察对象)。
2017-11-06 19:02:18 +01:00
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).
2018-03-08 17:07:55 +08:00
这节课,`HeroService.getHeroes()` 将会返回 `Observable`,因为它最终会使用 Angular 的 `HttpClient.get` 方法来获取英雄数据,而 [`HttpClient.get()` 会返回 `Observable`](guide/http)。
2017-11-06 19:02:18 +01:00
### Observable _HeroService_
2018-03-08 17:07:55 +08:00
### 可观察对象版本的 `HeroService`
2017-11-06 19:02:18 +01:00
`Observable` is one of the key classes in the [RxJS library](http://reactivex.io/rxjs/).
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
`Observable` 是 [RxJS 库](http://reactivex.io/rxjs/)中的一个关键类。
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
在[稍后的 HTTP 教程](tutorial/toh-pt6)中,你就会知道 Angular `HttpClient` 的方法会返回 RxJS 的 `Observable`
这节课,你将使用 RxJS 的 `of()` 函数来模拟从服务器返回数据。
2017-11-06 19:02:18 +01:00
Open the `HeroService` file and import the `Observable` and `of` symbols from RxJS.
2018-03-08 17:07:55 +08:00
打开 `HeroService` 文件,并从 RxJS 中导入 `Observable``of` 符号。
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/hero.service.ts"
header="src/app/hero.service.ts (Observable imports)" region="import-observable">
</code-example>
2017-11-06 19:02:18 +01:00
Replace the `getHeroes` method with this one.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
`getHeroes` 方法改成这样:
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes-1"></code-example>
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
`of(HEROES)` returns an `Observable<Hero[]>` that emits _a single value_, the array of mock heroes.
2018-03-08 17:07:55 +08:00
`of(HEROES)` 会返回一个 `Observable<Hero[]>`,它会发出单个值,这个值就是这些模拟英雄的数组。
<div class="alert is-helpful">
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
在 [HTTP 教程](tutorial/toh-pt6)中,你将会调用 `HttpClient.get<Hero[]>()` 它也同样返回一个 `Observable<Hero[]>`,它也会发出单个值,这个值就是来自 HTTP 响应体中的英雄数组。
2017-04-10 16:51:13 +01:00
</div>
2017-11-06 19:02:18 +01:00
### Subscribe in _HeroesComponent_
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
### 在 `HeroesComponent` 中订阅
2017-11-06 19:02:18 +01:00
The `HeroService.getHeroes` method used to return a `Hero[]`.
Now it returns an `Observable<Hero[]>`.
2017-04-01 01:57:13 +02:00
2018-05-16 17:22:40 +10:00
`HeroService.getHeroes` 方法之前返回一个 `Hero[]`
现在它返回的是 `Observable<Hero[]>`
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
You'll have to adjust to that difference in `HeroesComponent`.
2018-03-08 17:07:55 +08:00
你必须在 `HeroesComponent` 中也向本服务中的这种形式看齐。
2017-11-06 19:02:18 +01:00
Find the `getHeroes` method and replace it with the following code
(shown side-by-side with the previous version for comparison)
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
找到 `getHeroes` 方法,并且把它替换为如下代码(和前一个版本对比显示):
2017-11-06 19:02:18 +01:00
<code-tabs>
2017-04-01 01:57:13 +02:00
<code-pane header="heroes.component.ts (Observable)"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/heroes/heroes.component.ts" region="getHeroes">
</code-pane>
<code-pane header="heroes.component.ts (Original)"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="getHeroes">
</code-pane>
2017-11-06 19:02:18 +01:00
</code-tabs>
2017-11-06 19:02:18 +01:00
`Observable.subscribe()` is the critical difference.
2018-03-08 17:07:55 +08:00
`Observable.subscribe()` 是关键的差异点。
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
上一个版本把英雄的数组赋值给了该组件的 `heroes` 属性。
这种赋值是*同步*的,这里包含的假设是服务器能立即返回英雄数组或者浏览器能在等待服务器响应时冻结界面。
2017-11-06 19:02:18 +01:00
That _won't work_ when the `HeroService` is actually making requests of a remote server.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
`HeroService` 真的向远端服务器发起请求时,这种方式就行不通了。
2017-11-06 19:02:18 +01:00
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.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
新的版本等待 `Observable` 发出这个英雄数组,这可能立即发生,也可能会在几分钟之后。
然后,`subscribe` 函数把这个英雄数组传给这个回调函数,该函数把英雄数组赋值给组件的 `heroes` 属性。
2017-11-06 19:02:18 +01:00
This asynchronous approach _will work_ when
the `HeroService` requests heroes from the server.
2018-03-08 17:07:55 +08:00
使用这种异步方式,当 `HeroService` 从远端服务器获取英雄数据时,就*可以工作了*。
2017-11-06 19:02:18 +01:00
## Show messages
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
## 显示消息
2017-11-06 19:02:18 +01:00
In this section you will
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
在这一节,你将
2017-11-06 19:02:18 +01:00
* add a `MessagesComponent` that displays app messages at the bottom of the screen.
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
添加一个 `MessagesComponent`,它在屏幕的底部显示应用中的消息。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* create an injectable, app-wide `MessageService` for sending messages to be displayed
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
创建一个可注入的、全应用级别的 `MessageService`,用于发送要显示的消息。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* inject `MessageService` into the `HeroService`
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
`MessageService` 注入到 `HeroService` 中。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* display a message when `HeroService` fetches heroes successfully.
2018-03-24 13:12:42 +08:00
`HeroService` 成功获取了英雄数据时显示一条消息。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
### Create _MessagesComponent_
2018-03-08 17:07:55 +08:00
### 创建 `MessagesComponent`
2017-11-06 19:02:18 +01:00
Use the CLI to create the `MessagesComponent`.
2018-03-08 17:07:55 +08:00
使用 CLI 创建 `MessagesComponent`
2017-11-06 19:02:18 +01:00
<code-example language="sh" class="code-shell">
ng generate component messages
</code-example>
2017-11-06 19:02:18 +01:00
The CLI creates the component files in the `src/app/messages` folder and declare `MessagesComponent` in `AppModule`.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
CLI 在 `src/app/messages` 中创建了组件文件,并且把 `MessagesComponent` 声明在了 `AppModule` 中。
2017-11-06 19:02:18 +01:00
Modify the `AppComponent` template to display the generated `MessagesComponent`
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
修改 `AppComponent` 的模板来显示所生成的 `MessagesComponent`
2017-11-06 19:02:18 +01:00
<code-example
header = "/src/app/app.component.html"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/app.component.html">
</code-example>
2017-11-06 19:02:18 +01:00
You should see the default paragraph from `MessagesComponent` at the bottom of the page.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
你可以在页面的底部看到来自的 `MessagesComponent` 的默认内容。
2017-11-06 19:02:18 +01:00
### Create the _MessageService_
2018-03-08 17:07:55 +08:00
### 创建 `MessageService`
2017-11-06 19:02:18 +01:00
Use the CLI to create the `MessageService` in `src/app`.
2018-03-08 17:07:55 +08:00
使用 CLI 在 `src/app` 中创建 `MessageService`
2017-11-06 19:02:18 +01:00
<code-example language="sh" class="code-shell">
2018-05-15 13:32:40 +08:00
ng generate service message
</code-example>
2017-11-06 19:02:18 +01:00
Open `MessageService` and replace its contents with the following.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
打开 `MessageService`,并把它的内容改成这样:
2017-11-06 19:02:18 +01:00
<code-example
header = "/src/app/message.service.ts"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/message.service.ts">
</code-example>
2017-11-06 19:02:18 +01:00
The service exposes its cache of `messages` and two methods: one to `add()` a message to the cache and another to `clear()` the cache.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
该服务对外暴露了它的 `messages` 缓存,以及两个方法:`add()` 方法往缓存中添加一条消息,`clear()` 方法用于清空缓存。
2017-11-06 19:02:18 +01:00
{@a inject-message-service}
2018-03-03 21:06:01 +08:00
2017-11-06 19:02:18 +01:00
### Inject it into the `HeroService`
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
### 把它注入到 `HeroService` 中
2017-11-06 19:02:18 +01:00
Re-open the `HeroService` and import the `MessageService`.
2018-03-08 17:07:55 +08:00
重新打开 `HeroService`,并且导入 `MessageService`
2017-11-06 19:02:18 +01:00
<code-example
header = "/src/app/hero.service.ts (import MessageService)"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/hero.service.ts" region="import-message-service">
</code-example>
2017-11-06 19:02:18 +01:00
Modify the constructor with a parameter that declares a private `messageService` property.
Angular will inject the singleton `MessageService` into that property
when it creates the `HeroService`.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
修改这个构造函数,添加一个私有的 `messageService` 属性参数。
Angular 将会在创建 `HeroService` 时把 `MessageService` 的单例注入到这个属性中。
2017-11-06 19:02:18 +01:00
<code-example
path="toh-pt4/src/app/hero.service.ts" region="ctor">
</code-example>
<div class="alert is-helpful">
2017-11-06 19:02:18 +01:00
This is a typical "*service-in-service*" scenario:
you inject the `MessageService` into the `HeroService` which is injected into the `HeroesComponent`.
2018-03-08 17:07:55 +08:00
这是一个典型的“服务中的服务”场景:
你把 `MessageService` 注入到了 `HeroService` 中,而 `HeroService` 又被注入到了 `HeroesComponent` 中。
2017-04-10 16:51:13 +01:00
</div>
2017-11-06 19:02:18 +01:00
### Send a message from `HeroService`
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
### 从 `HeroService` 中发送一条消息
2017-11-06 19:02:18 +01:00
Modify the `getHeroes` method to send a message when the heroes are fetched.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
修改 `getHeroes` 方法,在获取到英雄数组时发送一条消息。
2017-11-06 19:02:18 +01:00
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes">
</code-example>
2017-11-06 19:02:18 +01:00
### Display the message from `HeroService`
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
### 从 `HeroService` 中显示消息
2017-11-06 19:02:18 +01:00
The `MessagesComponent` should display all messages,
including the message sent by the `HeroService` when it fetches heroes.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
`MessagesComponent` 可以显示所有消息,
包括当 `HeroService` 获取到英雄数据时发送的那条。
2017-11-06 19:02:18 +01:00
Open `MessagesComponent` and import the `MessageService`.
2018-03-08 17:07:55 +08:00
打开 `MessagesComponent`,并且导入 `MessageService`
2017-11-06 19:02:18 +01:00
<code-example
header = "/src/app/messages/messages.component.ts (import MessageService)"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/messages/messages.component.ts" region="import-message-service">
</code-example>
2017-11-06 19:02:18 +01:00
Modify the constructor with a parameter that declares a **public** `messageService` property.
Angular will inject the singleton `MessageService` into that property
2018-05-15 13:32:40 +08:00
when it creates the `MessagesComponent`.
2018-03-08 17:07:55 +08:00
修改构造函数,添加一个 **public**`messageService` 属性。
Angular 将会在创建 `MessagesComponent` 的实例时 把 `MessageService` 的实例注入到这个属性中。
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
<code-example
path="toh-pt4/src/app/messages/messages.component.ts" region="ctor">
</code-example>
2017-11-06 19:02:18 +01:00
The `messageService` property **must be public** because you're about to bind to it in the template.
2018-03-09 13:23:04 +08:00
这个 `messageService` 属性必须是公共属性,因为你将会在模板中绑定到它。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
<div class="alert is-important">
2017-11-06 19:02:18 +01:00
Angular only binds to _public_ component properties.
2018-03-09 13:23:04 +08:00
Angular 只会绑定到组件的*公共*属性。
2018-03-08 17:07:55 +08:00
2017-04-10 16:51:13 +01:00
</div>
2017-11-06 19:02:18 +01:00
### Bind to the _MessageService_
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
### 绑定到 `MessageService`
2017-11-06 19:02:18 +01:00
Replace the CLI-generated `MessagesComponent` template with the following.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
把 CLI 生成的 `MessagesComponent` 的模板改成这样:
2017-11-06 19:02:18 +01:00
<code-example
header = "src/app/messages/messages.component.html"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/messages/messages.component.html">
</code-example>
2017-11-06 19:02:18 +01:00
This template binds directly to the component's `messageService`.
2018-03-08 17:07:55 +08:00
这个模板直接绑定到了组件的 `messageService` 属性上。
2017-11-06 19:02:18 +01:00
* The `*ngIf` only displays the messages area if there are messages to show.
2018-08-23 21:51:48 +08:00
`*ngIf` 只有在有消息时才会显示消息区。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* An `*ngFor` presents the list of messages in repeated `<div>` elements.
2018-03-24 13:12:42 +08:00
`*ngFor` 用来在一系列 `<div>` 元素中展示消息列表。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* An Angular [event binding](guide/template-syntax#event-binding) binds the button's click event
to `MessageService.clear()`.
2018-03-24 13:12:42 +08:00
Angular 的[事件绑定](guide/template-syntax#event-binding)把按钮的 `click` 事件绑定到了 `MessageService.clear()`
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
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.
2018-03-08 17:07:55 +08:00
当你把 [最终代码](#final-code-review) 某一页的内容添加到 `messages.component.css` 中时,这些消息会变得好看一些。
2017-11-06 19:02:18 +01:00
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.
2017-04-01 01:57:13 +02:00
2018-03-08 17:07:55 +08:00
刷新浏览器,页面显示出了英雄列表。
滚动到底部,就会在消息区看到来自 `HeroService` 的消息。
点击“清空”按钮,消息区不见了。
2017-11-06 19:02:18 +01:00
{@a final-code-review}
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
## Final code review
2018-03-08 17:07:55 +08:00
## 查看最终代码
2017-11-06 19:02:18 +01:00
Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
2018-03-08 17:07:55 +08:00
你的应用应该变成了这样 <live-example></live-example>。本页所提及的代码文件如下:
<code-tabs>
<code-pane header="src/app/hero.service.ts"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/hero.service.ts">
</code-pane>
<code-pane header="src/app/message.service.ts"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/message.service.ts">
</code-pane>
<code-pane header="src/app/heroes/heroes.component.ts"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/heroes/heroes.component.ts">
</code-pane>
<code-pane header="src/app/messages/messages.component.ts"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/messages/messages.component.ts">
</code-pane>
<code-pane header="src/app/messages/messages.component.html"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/messages/messages.component.html">
</code-pane>
<code-pane header="src/app/messages/messages.component.css"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/messages/messages.component.css">
</code-pane>
<code-pane header="src/app/app.module.ts"
path="toh-pt4/src/app/app.module.ts">
</code-pane>
<code-pane header="src/app/app.component.html"
2017-11-06 19:02:18 +01:00
path="toh-pt4/src/app/app.component.html">
</code-pane>
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
</code-tabs>
2017-04-01 01:57:13 +02:00
## Summary
2018-03-03 21:06:01 +08:00
## 小结
2017-11-06 19:02:18 +01:00
* You refactored data access to the `HeroService` class.
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
你把数据访问逻辑重构到了 `HeroService` 类中。
2018-03-08 17:07:55 +08:00
2018-05-15 13:32:40 +08:00
* You registered the `HeroService` as the _provider_ of its service at the root level so that it can be injected anywhere in the app.
2018-03-03 21:06:01 +08:00
2018-05-15 13:32:40 +08:00
你在根注入器中把 `HeroService` 注册为该服务的提供商,以便在别处可以注入它。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* You used [Angular Dependency Injection](guide/dependency-injection) to inject it into a component.
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
你使用 [Angular 依赖注入](guide/dependency-injection)机制把它注入到了组件中。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* You gave the `HeroService` _get data_ method an asynchronous signature.
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
你给 `HeroService` 中获取数据的方法提供了一个异步的函数签名。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* You discovered `Observable` and the RxJS _Observable_ library.
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
你发现了 `Observable` 以及 RxJS 库。
2018-03-08 17:07:55 +08:00
2018-05-15 13:32:40 +08:00
* You used RxJS `of()` to return an observable of mock heroes (`Observable<Hero[]>`).
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
你使用 RxJS 的 `of()` 方法返回了一个模拟英雄数据的*可观察对象* (`Observable<Hero[]>`)。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* The component's `ngOnInit` lifecycle hook calls the `HeroService` method, not the constructor.
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
在组件的 `ngOnInit` 生命周期钩子中调用 `HeroService` 方法,而不是构造函数中。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* You created a `MessageService` for loosely-coupled communication between classes.
2018-03-03 21:06:01 +08:00
2018-03-24 13:12:42 +08:00
你创建了一个 `MessageService`,以便在类之间实现松耦合通讯。
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
* The `HeroService` injected into a component is created with another injected service,
`MessageService`.
2018-03-08 17:07:55 +08:00
2018-03-24 16:33:17 +08:00
`HeroService` 连同注入到它的服务 `MessageService` 一起,注入到了组件中。