665 lines
34 KiB
Plaintext
665 lines
34 KiB
Plaintext
include ../_util-fns
|
||
|
||
:marked
|
||
# Services
|
||
# 服务
|
||
The Tour of Heroes is evolving and we anticipate adding more components in the near future.
|
||
|
||
《英雄指南》继续前行。接下来,我们准备添加更多的组件。
|
||
|
||
Multiple components will need access to hero data and we don't want to copy and
|
||
paste the same code over and over.
|
||
Instead, we'll create a single reusable data service and learn to
|
||
inject it in the components that need it.
|
||
|
||
将来会有更多的组件访问英雄数据,但我们不想一遍又一遍的复制粘贴同样的代码。
|
||
我们的替代方案是,创建一个单一的、可复用的数据服务,然后学着把它注入到那些想用它的组件中去。
|
||
|
||
Refactoring data access to a separate service keeps the component lean and focused on supporting the view.
|
||
It also makes it easier to unit test the component with a mock service.
|
||
|
||
我们将重构数据访问代码,把它隔离到一个独立的服务中去,让组件尽可能保持精简,专注于为视图提供支持。
|
||
在这种方式下,借助mock服务来对组件进行单元测试也会更容易。
|
||
|
||
Because data services are invariably asynchronous,
|
||
we'll finish the chapter with a promise-based version of the data service.
|
||
|
||
因为数据服务通常都是异步的,所以在本章的最后,我们会把它重构为基于承诺(Promise,一种异步编程模式)的版本。
|
||
:marked
|
||
[Run the live example for part 4](/resources/live-examples/toh-4/ts/plnkr.html)
|
||
|
||
[运行第四部分的在线例子](/resources/live-examples/toh-4/ts/plnkr.html)
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Where We Left Off
|
||
## 我们在哪儿
|
||
Before we continue with our Tour of Heroes, let’s verify we have the following structure.
|
||
If not, we’ll need to go back and follow the previous chapters.
|
||
|
||
在继续《英雄指南》之前,先来检查一下,你是否已经有了如下目录结构。如果没有,你得先回上一章,看看错过了哪里。
|
||
|
||
.filetree
|
||
.file angular2-tour-of-heroes
|
||
.children
|
||
.file app
|
||
.children
|
||
.file app.component.ts
|
||
.file hero.ts
|
||
.file hero-detail.component.ts
|
||
.file main.ts
|
||
.file node_modules ...
|
||
.file typings ...
|
||
.file index.html
|
||
.file package.json
|
||
.file styles.css
|
||
.file systemjs.config.js
|
||
.file tsconfig.json
|
||
.file typings.json
|
||
:marked
|
||
### Keep the app transpiling and running
|
||
### 让应用代码保持转译和运行
|
||
Open a terminal/console window.
|
||
Start the TypeScript compiler, watch for changes, and start our server by entering the command:
|
||
|
||
打开terminal/console窗口,启动TypeScript编译器,它会监视文件变更,并启动开发服务器。只要敲:
|
||
|
||
code-example(format="." language="bash").
|
||
npm start
|
||
|
||
:marked
|
||
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||
|
||
当我们继续构建《英雄指南》时,应用会自动运行和更新。
|
||
|
||
## Creating a Hero Service
|
||
## 创建英雄服务
|
||
Our stakeholders have shared their larger vision for our app.
|
||
They tell us they want to show the heroes in various ways on different pages.
|
||
We already can select a hero from a list.
|
||
Soon we'll add a dashboard with the top performing heroes and create a separate view for editing hero details.
|
||
All three views need hero data.
|
||
|
||
客户向我们描绘了本应用更大的目标。
|
||
他们说,想要在不同的页面中用多种方式显示英雄。
|
||
现在我们已经能从列表中选择一个英雄了,但这还不够。
|
||
很快,我们将添加一个仪表盘来表彰绩效最好的英雄,并且创建一个独立视图来编辑英雄的详情。
|
||
所有这些视图都需要英雄的数据。
|
||
|
||
At the moment the `AppComponent` defines mock heroes for display.
|
||
We have at least two objections.
|
||
First, defining heroes is not the component's job.
|
||
Second, we can't easily share that list of heroes with other components and views.
|
||
|
||
目前,`AppComponent`显示的是我们自定义的一个mock英雄数据。
|
||
我们可改进的地方至少有两个:首先,定义英雄的数据不该是该组件的任务。其次,想把这份英雄列表的数据共享给其它组件和视图可不那么容易。
|
||
|
||
We can refactor this hero data acquisition business to a single service that provides heroes and
|
||
share that service with all components that need heroes.
|
||
|
||
我们可以把提供英雄数据的任务重构为一个单独的服务,它将提供英雄数据,并且把这个服务在所有需要英雄数据的组件之间共享。
|
||
|
||
### Create the HeroService
|
||
### 创建HeroService
|
||
Create a file in the `app` folder called `hero.service.ts`.
|
||
|
||
在`app`目录下创建一个名叫`hero.service.ts`的文件。
|
||
.l-sub-section
|
||
:marked
|
||
We've adopted a convention in which we spell the name of a service in lowercase followed by `.service`.
|
||
If the service name were multi-word, we'd spell the base filename with lower dash case (AKA "kebab-case").
|
||
The `SpecialSuperHeroService` would be defined in the `special-super-hero.service.ts` file.
|
||
|
||
我们遵循的文件命名约定是:服务名称的小写形式(基本名),加上`.service`后缀。
|
||
如果服务名称包含多个单词,我们就把基本名部分写成中线形式(dash-case,也被称作烤串形式kebab-case)。
|
||
比如,`SpecialSuperHeroService`服务应该被定义在`special-super-hero.service.ts`文件中。
|
||
:marked
|
||
We name the class `HeroService` and export it for others to import.
|
||
|
||
我们把这个类命名为`HeroService`,并且导出它,以供别人使用。
|
||
|
||
+makeExample('toh-4/ts/app/hero.service.1.ts', 'empty-class', 'hero.service.ts (导出类)')(format=".")
|
||
|
||
:marked
|
||
### Injectable Services
|
||
### 可注入的服务
|
||
Notice that we imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator.
|
||
|
||
注意,我们引入了Angular的`Injectable`函数,并通过`@Injectable()`装饰器使用这个函数。
|
||
.callout.is-helpful
|
||
:marked
|
||
**Don't forget the parentheses!** Neglecting them leads to an error that's difficult to diagnose.
|
||
|
||
**不要忘了写圆括号!**如果忘了写,就会导致一个很难诊断的错误。
|
||
:marked
|
||
TypeScript sees the `@Injectable()` decorator and emits metadata about our service,
|
||
metadata that Angular may need to inject other dependencies into this service.
|
||
|
||
当TypeScript看到`@Injectable()`装饰器时,就会记下本服务的元数据。如果Angular需要往这个服务中注入其它依赖,就会使用这些元数据。
|
||
|
||
The `HeroService` doesn't have any dependencies *at the moment*. Add the decorator anyway.
|
||
It is a "best practice" to apply the `@Injectable()` decorator *from the start*
|
||
both for consistency and for future-proofing.
|
||
|
||
此刻,`HeroService`还没有任何依赖,但我们还是得加上这个装饰器。作为一项最佳实践,无论是出于提高统一性还是减少变更的目的,都应该从一开始就加上`@Injectable()`装饰器。
|
||
|
||
:marked
|
||
### Getting Heroes
|
||
### 获取英雄
|
||
Add a `getHeroes` method stub.
|
||
|
||
添加一个名叫`getHeros`的桩方法。
|
||
+makeExample('toh-4/ts/app/hero.service.1.ts', 'getHeroes-stub', 'hero.service.ts (getHeroes桩方法)')(format=".")
|
||
:marked
|
||
We're holding back on the implementation for a moment to make an important point.
|
||
|
||
在这个实现上暂停一下,我们先来讲一个重点。
|
||
|
||
The consumer of our service doesn't know how the service gets the data.
|
||
Our `HeroService` could get `Hero` data from anywhere.
|
||
It could get the data from a web service or local storage
|
||
or from a mock data source.
|
||
|
||
消费者并不知道本服务会如何获取数据。
|
||
我们的`HeroService`服务可以从任何地方获取英雄的数据。
|
||
它可以从网络服务器获取,可以从浏览器的局部存储区获取,也可以是直接写在源码中的mock数据。
|
||
|
||
That's the beauty of removing data access from the component.
|
||
We can change our minds about the implementation as often as we like,
|
||
for whatever reason, without touching any of the components that need heroes.
|
||
|
||
我们从组件中成功移除了数据访问代码,干得漂亮。
|
||
这下子,我们可以随时改变数据访问的实现方式了。
|
||
|
||
### Mock Heroes
|
||
### Mock英雄数据
|
||
We already have mock `Hero` data sitting in the `AppComponent`. It doesn't belong there. It doesn't belong *here* either.
|
||
We'll move the mock data to its own file.
|
||
|
||
我们曾在`AppComponent`组件中写过mock版的英雄数据。它不该在那里,但也不该在*这里*!我们得把mock数据移到它自己的文件中去。
|
||
|
||
Cut the `HEROES` array from `app.component.ts` and paste it to a new file in the `app` folder named `mock-heroes.ts`.
|
||
We copy the `import {Hero} ...` statement as well because the heroes array uses the `Hero` class.
|
||
|
||
从`app.component.ts`文件中剪切`HEROS`数组,并把它粘贴到`app`目录下一个名叫`mock-heroes.ts`的文件中。
|
||
我们还要把`import {Hero}...`语句拷贝过来,因为我们的英雄数组用到了`Hero`类。
|
||
+makeExample('toh-4/ts/app/mock-heroes.ts', null, 'mock-heroes.ts (英雄数组)')
|
||
:marked
|
||
We export the `HEROES` constant so we can import it elsewhere — such as our `HeroService`.
|
||
|
||
我们导出了`HEROES`常量,以便在其它地方导入它 —— 比如`HeroService`服务。
|
||
|
||
Meanwhile, back in `app.component.ts` where we cut away the `HEROES` array,
|
||
we leave behind an uninitialized `heroes` property:
|
||
|
||
同时,回到刚剪切出`HEROES`数组的`app.component.ts`文件,我们留下了一个尚未初始化的`heroes`属性:
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'heroes-prop', 'app.component.ts (heroes属性)')(format=".")
|
||
:marked
|
||
### Return Mocked Heroes
|
||
### 返回模拟的英雄数据
|
||
Back in the `HeroService` we import the mock `HEROES` and return it from the `getHeroes` method.
|
||
Our `HeroService` looks like this:
|
||
|
||
回到`HeroService`,我们导入`HEROES`常量,并在`getHeroes`方法中返回它。
|
||
我们的`HeroService`服务现在看起来是这样:
|
||
+makeExample('toh-4/ts/app/hero.service.1.ts', null, 'hero.service.ts')(format=".")
|
||
:marked
|
||
### Use the Hero Service
|
||
### 使用HeroService服务
|
||
We're ready to use the `HeroService` in other components starting with our `AppComponent`.
|
||
|
||
我们已经在包括`AppComponent`在内的多个组件中使用了`HeroService`服务。
|
||
|
||
We begin, as usual, by importing the thing we want to use, the `HeroService`.
|
||
|
||
通常,我们会从导入要用的东西开始,比如`HeroService`。
|
||
+makeExample('toh-4/ts/app/app.component.ts', 'hero-service-import', 'app.component.ts (导入HeroService)')
|
||
:marked
|
||
Importing the service allows us to *reference* it in our code.
|
||
How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
|
||
|
||
导入这个服务让我们可以在代码中引用它。
|
||
`AppComponent`该如何在运行中获得一个具体的`HeroService`实例呢?
|
||
|
||
### Do we *new* the *HeroService*? No way!
|
||
### 我们要自己 *new* 出这个 *HeroService* 吗?没门!
|
||
We could create a new instance of the `HeroService` with "new" like this:
|
||
|
||
固然,我们可以使用`new`关键字来创建`HeroService`的实例,就像这样:
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'new-service')(format=".")
|
||
:marked
|
||
That's a bad idea for several reasons including
|
||
|
||
但这不是个好主意,有很多理由,比如:
|
||
|
||
* Our component has to know how to create a `HeroService`.
|
||
If we ever change the `HeroService` constructor,
|
||
we'll have to find every place we create the service and fix it.
|
||
Running around patching code is error prone and adds to the test burden.
|
||
|
||
* 我们的组件将不得不懂得该如何创建`HeroService`。
|
||
如果有一天我们修改了`HeroService`的构造函数,我们不得不找出创建过此服务的每一处代码,并修改它。
|
||
而给代码打补丁的行为容易导致错误,并增加了测试的负担。
|
||
|
||
* We create a new service each time we use "new".
|
||
What if the service should cache heroes and share that cache with others?
|
||
We couldn't do that.
|
||
|
||
* 我们每次使用`new`都会创建一个新的服务实例。
|
||
如果这个服务需要缓存英雄列表,并把这个缓存共享给别人呢?怎么办?
|
||
没办法,做不到。
|
||
|
||
* We're locking the `AppComponent` into a specific implementation of the `HeroService`.
|
||
It will be hard to switch implementations for different scenarios.
|
||
Can we operate offline?
|
||
Will we need different mocked versions under test?
|
||
Not easy.
|
||
|
||
* 我们把`AppComponent`锁死在`HeroService`的一个特定实现中。
|
||
我们很难在别的场景中把它换成别的实现。
|
||
比如,能离线操作吗?能在测试时使用不同的模拟版本吗?这可不容易。
|
||
|
||
*What if ... what if ... Hey, we've got work to do!*
|
||
* 如果……如果……嘿!这下我们可有得忙了!
|
||
|
||
We get it. Really we do.
|
||
But it is so ridiculously easy to avoid these problems that there is no excuse for doing it wrong.
|
||
|
||
有办法了,真的!这个办法真是简单得不可思议,它能解决这些问题,你就再也没有犯错误的接口了。
|
||
|
||
### Inject the *HeroService*
|
||
### 注入 *HeroService*
|
||
|
||
Two lines replace the one line of *new*:
|
||
|
||
用这两行代码代替用`new`时的一行:
|
||
1. we add a constructor.
|
||
1. 添加一个构造函数
|
||
1. we add to the component's `providers` metadata
|
||
1. 添加组件的`providers`元数据
|
||
|
||
Here's the constructor:
|
||
|
||
下面就是这个构造函数:
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'ctor', 'app.component.ts (构造函数)')
|
||
:marked
|
||
The constructor itself does nothing. The parameter simultaneously
|
||
defines a private `_heroService` property and identifies it as a `HeroService` injection site.
|
||
|
||
构造函数自己什么也不用做,它在参数中定义了一个私有的`_heroService`属性,并把它标记为注入`HeroService`的靶点。
|
||
.l-sub-section
|
||
:marked
|
||
We prefix private variables with an underscore (_) to warn readers of our code
|
||
that this variable is not part of the component's public API.
|
||
|
||
我们给私有变量添加下划线(_)前缀,用以警告这些代码的读者:这个变量不是组件的公开API的一部分。
|
||
:marked
|
||
Now Angular will know to supply an instance of the `HeroService` when it creates a new `AppComponent`.
|
||
|
||
现在,Angular将会知道,当它创建`AppComponent`实例时,需要先提供一个`HeroService`的实例。
|
||
|
||
Angular has to get that instance from somewhere. That's the role of the Angular *Dependency Injector*.
|
||
The **Injector** has a **container** of previously created services.
|
||
Either it finds and returns a pre-existing `HeroService` from its container or it creates a new instance, adds
|
||
it to the container, and returns it to Angular.
|
||
|
||
Angular得想办法获得这个实例。这就是Angular *依赖注入器(Dependency Injector)* 扮演的角色。
|
||
这个 **注入器** 有一个包括以前创建过的所有服务的 **容器** 。
|
||
它既可以从容器中查找并返回一个已存在的`HeroService`实例,又可以创建一个新的实例,把它添加到容器中,然后把它返回给Angular。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Learn more about Dependency Injection in the [Dependency Injection](../guide/dependency-injection.html) chapter.
|
||
|
||
要了解关于依赖注入的更多知识,请参见[依赖注入](../guide/dependency-injection.html)一章。
|
||
|
||
:marked
|
||
The *injector* does not know yet how to create a `HeroService`.
|
||
If we ran our code now, Angular would fail with an error:
|
||
|
||
*注入器*还不知道该如何创建`HeroService`。
|
||
如果现在运行我们的代码,Angular就会失败,并报错:
|
||
code-example(format="." language="html").
|
||
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
|
||
|
||
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService) —— 异常:没有HeroService的提供者(provider)!(AppComponent -> HeroService)
|
||
:marked
|
||
We have to teach the *injector* how to make a `HeroService` by registering a `HeroService` **provider**.
|
||
Do that by adding the following `providers` array property to the bottom of the component metadata
|
||
in the `@Component` call.
|
||
|
||
我们还得注册一个`HeroService`**Provider**,来告诉*注入器*如何创建`HeroService`。
|
||
要做到这一点,我们应该在`@Component`组件的元数据底部添加`providers`数组属性如下:
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'providers', 'app.component.ts (提供HeroService)')
|
||
:marked
|
||
The `providers` array tells Angular to create a fresh instance of the `HeroService` when it creates a new `AppComponent`.
|
||
The `AppComponent` can use that service to get heroes and so can every child component of its component tree.
|
||
|
||
`providers`数组告诉Angular,在创建一个新的`AppComponent`时,也要创建一个`HeroService`的新鲜实例。
|
||
`AppComponent`可以用此服务来获取英雄列表,它组件树中的每一个子组件也可以这么做。
|
||
<a id="child-component"></a>
|
||
.l-sub-section
|
||
:marked
|
||
### Services and the component tree
|
||
### 服务和组件树
|
||
|
||
Recall that the `AppComponent` creates an instance of `HeroDetail` by virtue of the
|
||
`<my-hero-detail>` tag at the bottom of its template. That `HeroDetail` is a child of the `AppComponent`.
|
||
|
||
回忆一下,`AppComponent`在它的模板底部包含了一个`<my-hero-detail>`标签,于是创建了一个`HeroDetail`的实例。这个`HeroDetail`就叫做`AppComponent`的子组件。
|
||
|
||
If the `HeroDetailComponent` needed its parent component's `HeroService`,
|
||
it would ask Angular to inject the service into its constructor which would look just like the one for `AppComponent`:
|
||
|
||
如果`HeroDetailComponent`需要访问来自它父组件的`HeroService`服务,它可以要求Angular把这个服务注入到自己的构造函数中 —— 就像在`AppComponent`中的做法一样。
|
||
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'ctor', 'hero-detail.component.ts (构造函数)')
|
||
:marked
|
||
The `HeroDetailComponent` must *not* repeat its parent's `providers` array! Guess [why](#shadow-provider).
|
||
|
||
`HeroDetailComponent`不能再写一遍它父组件的`providers`数组!你猜这是[为什么](#shadow-provider)。
|
||
|
||
The `AppComponent` is the top level component of our application.
|
||
There should be only one instance of that component and only one instance of the `HeroService` in our entire app.
|
||
|
||
`AppComponent`是我们应用的顶层组件。在我们的整个应用中,应该有唯一的一个顶层组件,应该有唯一的一个`HeroService`实例。
|
||
:marked
|
||
### *getHeroes* in the *AppComponent*
|
||
### *AppComponent* 中的 *getHeroes*
|
||
We've got the service in a `_heroService` private variable. Let's use it.
|
||
|
||
我们已经获得了此服务,并把它存入了私有变量`_heroService`中。我们这就开始使用它。
|
||
|
||
We pause to think. We can call the service and get the data in one line.
|
||
|
||
停下来想一想。我们可以在同一行内调用此服务并获得数据。
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'get-heroes')(format=".")
|
||
:marked
|
||
We don't really need a dedicated method to wrap one line. We write it anyway:
|
||
|
||
在真实的世界中,我们并不需要把一行代码包装成一个专门的方法,但无论如何,我们在演示代码中先这么写:
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'getHeroes', 'app.component.ts (getHeroes)')(format=".")
|
||
|
||
<a id="oninit"></a>
|
||
:marked
|
||
### The *ngOnInit* Lifecycle Hook
|
||
### *ngOnInit* 生命周期钩子
|
||
`AppComponent` should fetch and display heroes without a fuss.
|
||
Where do we call the `getHeroes` method? In a constructor? We do *not*!
|
||
|
||
毫无疑问,`AppComponent`应该获取英雄数据并显示它。
|
||
我们该在哪里调用`getHeroes`方法呢?在构造函数中吗? *不* !
|
||
|
||
Years of experience and bitter tears have taught us to keep complex logic out of the constructor,
|
||
especially anything that might call a server as a data access method is sure to do.
|
||
|
||
多年的经验和惨痛的教训教育我们,应该把复杂的逻辑扔到构造函数外面去,特别是那些需要从服务器获取数据的逻辑更是如此。
|
||
|
||
The constructor is for simple initializations like wiring constructor parameters to properties.
|
||
It's not for heavy lifting. We should be able to create a component in a test and not worry that it
|
||
might do real work — like calling a server! — before we tell it to do so.
|
||
|
||
构造函数是为了简单的初始化工作而设计的,比如把构造函数的参数赋值给属性。
|
||
它的负担不应该过于沉重。我们应该能在测试中创建一个组件,而不用担心它会做实际的工作 —— 比如和服务器通讯,直到我们主动要求它做这些。
|
||
|
||
If not the constructor, something has to call `getHeroes`.
|
||
|
||
如果不在构造函数中,总得有地方调用`getHeroes`吧。
|
||
|
||
Angular will call it if we implement the Angular **ngOnInit** *Lifecycle Hook*.
|
||
Angular offers a number of interfaces for tapping into critical moments in the component lifecycle:
|
||
at creation, after each change, and at its eventual destruction.
|
||
|
||
这也不难。只要我们实现了Angular的 **ngOnInit** *生命周期钩子* ,Angular就会主动调用这个钩子。
|
||
Angular提供了一些接口,用来介入组件生命周期的几个关键时间点:刚创建时、每次变化时,以及最终被销毁时。
|
||
|
||
Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
|
||
|
||
每个接口都有唯一的一个方法。只要组件实现了这个方法,Angular就会在合适的时机调用它。
|
||
.l-sub-section
|
||
:marked
|
||
Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.
|
||
|
||
要了解关于生命周期钩子的更多知识,请参见 [生命周期钩子](../guide/lifecycle-hooks.html) 一章。
|
||
:marked
|
||
Here's the essential outline for the `OnInit` interface:
|
||
|
||
这是`OnInit`接口的基本轮廓:
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'on-init', 'app.component.ts (OnInit协议)')(format=".")
|
||
:marked
|
||
We write an `ngOnInit` method with our initialization logic inside and leave it to Angular to call it
|
||
at the right time. In our case, we initialize by calling `getHeroes`.
|
||
|
||
我们写下带有初始化逻辑的`ngOnInit`方法,然后留给Angular,供其在正确的时机调用。在这个例子中,我们通过调用`getHeroes`来完成初始化。
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'ng-on-init', 'app.component.ts (OnInit协议)')(format=".")
|
||
:marked
|
||
Our application should be running as expected, showing a list of heroes and a hero detail view
|
||
when we click on a hero name.
|
||
|
||
我们的应用将会像期望的那样运行,显示英雄列表,并且在我们点击英雄的名字时,显示英雄的详情。
|
||
|
||
We're getting closer. But something isn't quite right.
|
||
|
||
我们就快完成了,但还有点事情不太对劲儿。
|
||
|
||
## Async Services and Promises
|
||
## 异步服务与承诺(Promise)
|
||
Our `HeroService` returns a list of mock heroes immediately.
|
||
Its `getHeroes` signature is synchronous
|
||
|
||
我们的`HeroService`立即返回一个模拟的英雄列表,它的`getHeroes`函数签名是同步的。
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'get-heroes')(format=".")
|
||
:marked
|
||
Ask for heroes and they are there in the returned result.
|
||
|
||
请求英雄数据,并直接在结果中返回它。
|
||
|
||
Someday we're going to get heroes from a remote server. We don’t call http yet, but we aspire to in later chapters.
|
||
|
||
将来,我们会从远端服务器上获取英雄数据。我们还没调用http,但在未来的章节中我们会希望这么做。
|
||
|
||
When we do, we'll have to wait for the server to respond and we won't be able to block the UI while we wait,
|
||
even if we want to (which we shouldn't) because the browser won't block.
|
||
|
||
那时候,我们不得不等待服务器返回,并且在等待时,我们没法停止UI响应,即使我们想这么做,也做不到,因为浏览器不会停止。
|
||
|
||
We'll have to use some kind of asynchronous technique and that will change the signature of our `getHeroes` method.
|
||
|
||
我们不得不使用一些异步技术,而这将改变`getHeroes`方法的签名。
|
||
|
||
We'll use *promises*.
|
||
|
||
我们将使用 *承诺(Promise)* 。
|
||
|
||
### The Hero Service makes a promise
|
||
### `HeroService`会生成一个承诺
|
||
|
||
A **promise** is ... well it's a promise to call us back later when the results are ready.
|
||
We ask an asynchronous service to do some work and give it a callback function.
|
||
It does that work (somewhere) and eventually it calls our function with the results of the work or an error.
|
||
|
||
**承诺** 就是 …… 好吧,它就是一个承诺 —— 在有了结果时,它承诺会回调我们。
|
||
我们请求一个异步服务去做点什么,然后给它一个回调函数。
|
||
它会去做(无论用哪种方式),一旦完成,它就会调用我们的回调函数,并通过参数把工作成果或者错误信息传给我们。
|
||
.l-sub-section
|
||
:marked
|
||
We are simplifying. Learn about ES2015 Promises [here](http://exploringjs.com/es6/ch_promises.html) and elsewhere on the web.
|
||
|
||
这里只是粗浅的说说,要了解更多,请参见[这里](http://exploringjs.com/es6/ch_promises.html)或在Web上搜索其它学习资源。
|
||
:marked
|
||
Update the `HeroService` with this promise-returning `getHeroes` method:
|
||
|
||
把`HeroService`的`getHeroes`方法改写为返回承诺的形式:
|
||
+makeExample('toh-4/ts/app/hero.service.ts', 'get-heroes', 'hero.service.ts (getHeroes)')(format=".")
|
||
:marked
|
||
We're still mocking the data. We're simulating the behavior of an ultra-fast, zero-latency server,
|
||
by returning an **immediately resolved promise** with our mock heroes as the result.
|
||
|
||
我们继续使用模拟数据。我们通过返回一个 *立即解决的承诺* 的方式,模拟了一个超快、零延迟的超级服务器。
|
||
### Act on the Promise
|
||
### 基于承诺的行动
|
||
Returning to the `AppComponent` and its `getHeroes` method, we see that it still looks like this:
|
||
|
||
回到`AppComponent`和它的`getHeroes`方法,我们看到它看起来还是这样的:
|
||
+makeExample('toh-4/ts/app/app.component.1.ts', 'getHeroes', 'app.component.ts (getHeroes - 老的)')(format=".")
|
||
:marked
|
||
As a result of our change to `HeroService`, we're now setting `this.heroes` to a promise rather than an array of heroes.
|
||
|
||
在修改了`HeroService`之后,我们还要把`this.heroes`替换为一个承诺,而不再是一个英雄数组。
|
||
|
||
We have to change our implementation to *act on the promise when it resolves*.
|
||
When the promise resolves successfully, *then* we will have heroes to display.
|
||
|
||
我们得修改这个实现,把它变成*基于承诺*的,并在承诺的事情被解决时再行动。
|
||
一旦承诺的事情被成功解决,我们就会显示英雄数据。
|
||
|
||
We pass our callback function as an argument to the promise's **then** method:
|
||
|
||
我们把回调函数作为参数传给承诺对象的**then**函数:
|
||
+makeExample('toh-4/ts/app/app.component.ts', 'get-heroes', 'app.component.ts (getHeroes - 修改版)')(format=".")
|
||
.l-sub-section
|
||
:marked
|
||
The [ES2015 arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
|
||
in the callback is more succinct than the equivalent function expression and gracefully handles *this*.
|
||
|
||
回调中所用的[ES2015箭头函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
|
||
能比等价的函数表达式更加快速、优雅的处理*this*指针。
|
||
:marked
|
||
Our callback sets the component's `heroes` property to the array of heroes returned by the service. That's all there is to it!
|
||
|
||
在回调中,我们把由服务返回的英雄数组赋值给组件的`heroes`属性。是的,这就搞定了。
|
||
|
||
Our app should still be running, still showing a list of heroes, and still
|
||
responding to a name selection with a detail view.
|
||
|
||
我们的程序仍在运行,仍在显示英雄列表,在选择英雄时,仍然会把他/她显示在详情页面中。
|
||
.l-sub-section
|
||
:marked
|
||
Checkout the "[Take it slow](#slow)" appendix to see what the app might be like with a poor connection.
|
||
|
||
查看附件中的“[慢一点儿](#slow)”一节,来了解在较差的网络连接中这个应用会是什么样的。
|
||
:marked
|
||
### Review the App Structure
|
||
### 回顾本应用的结构
|
||
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||
|
||
再检查下,经历了本章的所有重构之后,我们应该有了下列文件结构:
|
||
|
||
.filetree
|
||
.file angular2-tour-of-heroes
|
||
.children
|
||
.file app
|
||
.children
|
||
.file app.component.ts
|
||
.file hero.ts
|
||
.file hero-detail.component.ts
|
||
.file hero.service.ts
|
||
.file main.ts
|
||
.file mock-heroes.ts
|
||
.file node_modules ...
|
||
.file typings ...
|
||
.file index.html
|
||
.file package.json
|
||
.file tsconfig.json
|
||
.file typings.json
|
||
:marked
|
||
Here are the code files we discussed in this chapter.
|
||
|
||
这就是我们在本章讨论过的这些源码文件:
|
||
|
||
+makeTabs(`
|
||
toh-4/ts/app/hero.service.ts,
|
||
toh-4/ts/app/app.component.ts,
|
||
toh-4/ts/app/mock-heroes.ts
|
||
`,'',`
|
||
app/hero.service.ts,
|
||
app/app.component.ts,
|
||
app/mock-heroes.ts
|
||
`)
|
||
:marked
|
||
## The Road We’ve Travelled
|
||
## 走过的路
|
||
Let’s take stock of what we’ve built.
|
||
|
||
来盘点一下我们已经构建完的部分。
|
||
|
||
* We created a service class that can be shared by many components
|
||
* 我们创建了一个能被多个组件共享的服务类
|
||
* We used the `ngOnInit` Lifecycle Hook to get our heroes when our `AppComponent` activates
|
||
* 我们使用`ngOnInit`生命周期钩子,以便在`AppComponent`激活时获取英雄数据。
|
||
* We defined our `HeroService` as a provider for our `AppComponent`
|
||
* 我们把`HeroService`定义为`AppComponent`的一个供应商(provider)
|
||
* We created mock hero data and imported them into our service
|
||
* 我们创建了一个模拟的英雄数据,并把它导入我们的服务中
|
||
* We designed our service to return a promise and our component to get our data from the promise
|
||
* 我们把服务改造为返回承诺的,并让组件从承诺获取数据
|
||
|
||
[Run the live example for part 4](/resources/live-examples/toh-4/ts/plnkr.html)
|
||
|
||
[运行第四部分的在线例子](/resources/live-examples/toh-4/ts/plnkr.html)
|
||
|
||
### The Road Ahead
|
||
### 前方的路
|
||
Our Tour of Heroes has become more reusable using shared components and services.
|
||
We want to create a dashboard, add menu links that route between the views, and format data in a template.
|
||
As our app evolves, we’ll learn how to design it to make it easier to grow and maintain.
|
||
|
||
通过使用共享组件和服务,我们的《英雄指南》更有复用性了。
|
||
我们还要创建一个仪表盘,要添加菜单链接,要路由到各个视图,还要在模板中格式化数据。
|
||
随着我们应用的进化,我们还会学到如何进行设计,让它更易于扩展和维护。
|
||
|
||
We learn about Angular Component Router and navigation among the views in the [next tutorial](toh-pt5.html) chapter.
|
||
|
||
我们将在[下一章](toh-pt5.html)学习Angular组件路由,以及在视图间导航的知识。
|
||
|
||
.l-main-section
|
||
<a id="slow"></a>
|
||
:marked
|
||
### Appendix: Take it slow
|
||
### 附件:慢一点儿
|
||
|
||
We can simulate a slow connection.
|
||
|
||
我们可以模拟慢速连接。
|
||
|
||
Import the `Hero` symbol and add the following `getHeroesSlowly` method to the `HeroService`
|
||
|
||
导入`Hero`类,并且在`HeroService`中添加如下的`getHeroesSlowly`方法:
|
||
+makeExample('toh-4/ts/app/hero.service.ts', 'get-heroes-slowly', 'hero.service.ts (getHeroesSlowy)')(format=".")
|
||
:marked
|
||
Like `getHeroes`, it also returns a promise.
|
||
But this promise waits 2 seconds before resolving the promise with mock heroes.
|
||
|
||
像`getHeroes`一样,它也返回一个承诺。
|
||
但是,这个承诺会在使用模拟数据完成任务之前等待两秒钟。
|
||
|
||
Back in the `AppComponent`, replace `_heroService.getHeroes` with `_heroService.getHeroesSlowly`
|
||
and see how the app behaves.
|
||
|
||
回到`AppComponent`中,把`_heroService.getHeroes`替换为`_heroService.getHeroesSlowly`,看看应用的行为有什么变化。
|
||
|
||
.l-main-section
|
||
<a id="shadow-provider"></a>
|
||
:marked
|
||
### Appendix: Shadowing the parent's service
|
||
### 附件:遮蔽父组件的服务
|
||
|
||
We stated [earlier](#child-component) that if we injected the parent `AppComponent` `HeroService`
|
||
into the `HeroDetailComponent`, *we must not add a providers array* to the `HeroDetailComponent` metadata.
|
||
|
||
我们在[前面](#child-component)说过,如果在父组件`AppComponent`中把`HeroService`服务注入到`HeroDetailComponent`,
|
||
*我们就不应该在`HeroDetailComponent`的元数据中再添加一个providers数组*。
|
||
|
||
Why? Because that tells Angular to create a new instance of the `HeroService` at the `HeroDetailComponent` level.
|
||
The `HeroDetailComponent` doesn't want its *own* service instance; it wants its *parent's* service instance.
|
||
Adding the `providers` array creates a new service instance that shadows the parent instance.
|
||
|
||
为什么呢?因为那会告诉Angular在`HeroDetailComponent`这一层创建一个新的`HeroService`实例。
|
||
显然,在这个例子中,`HeroDetailComponent`不会希望创建*自己的*服务实例,它想要的就是来自父组件的服务实例。
|
||
给组件添加一个`providers`数组(即:注册provider),就会创建一个新的服务实例,而它会遮蔽父组件中的同名实例。
|
||
|
||
Think carefully about where and when to register a provider.
|
||
Understand the scope of that registration. Be careful not to create a new service instance at the wrong level.
|
||
|
||
务必想清楚,在哪里、在什么时候注册Provider。要理解注册的有效范围。小心点!不要在错误的级别上创建新的服务实例。
|