From 3cf6ca5f7e0446e869050aca03549908cdb58053 Mon Sep 17 00:00:00 2001 From: Zhicheng Wang Date: Sat, 16 Apr 2016 16:43:13 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=9B=9B=E7=AB=A0=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=BA=86=E4=B8=80=E5=8D=8A=E5=84=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/docs/ts/latest/tutorial/toh-pt2.jade | 6 +- public/docs/ts/latest/tutorial/toh-pt3.jade | 6 +- public/docs/ts/latest/tutorial/toh-pt4.jade | 164 +++++++++++++++++++- 3 files changed, 162 insertions(+), 14 deletions(-) diff --git a/public/docs/ts/latest/tutorial/toh-pt2.jade b/public/docs/ts/latest/tutorial/toh-pt2.jade index 21d026624a..3f57bb2f85 100644 --- a/public/docs/ts/latest/tutorial/toh-pt2.jade +++ b/public/docs/ts/latest/tutorial/toh-pt2.jade @@ -23,12 +23,12 @@ include ../_util-fns .l-main-section :marked ## Where We Left Off - ## 我们从哪里出发 + ## 我们在哪儿 Before we continue with Part 2 of the Tour of Heroes, let’s verify we have the following structure after [Part 1](./toh-pt1.html). If not, we’ll need to go back to Part 1 and figure out what we missed. - 在继续《英雄之旅》的第二部分之前,我们先检查一下,完成第一部分之后,你是否已经有了如下目录结构。如果没有,你得先回到第一部分,看看缺了哪里。 + 在继续《英雄之旅》的第二部分之前,我们先检查一下,完成[第一部分](./toh-pt1.html)之后,你是否已经有了如下目录结构。如果没有,你得先回到第一部分,看看缺了哪里。 .filetree .file angular2-tour-of-heroes @@ -48,7 +48,7 @@ include ../_util-fns ### 让应用代码保持转译和运行 We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing - 我们要启动TypeScript编译器,它会监视文件变更,并且启动开发服务器。我们只要敲: + 我们要启动TypeScript编译器,它会监视文件变更,并启动开发服务器。我们只要敲: code-example(format="." language="bash"). npm start diff --git a/public/docs/ts/latest/tutorial/toh-pt3.jade b/public/docs/ts/latest/tutorial/toh-pt3.jade index f64b690d12..d9a7bcfc84 100644 --- a/public/docs/ts/latest/tutorial/toh-pt3.jade +++ b/public/docs/ts/latest/tutorial/toh-pt3.jade @@ -14,10 +14,10 @@ include ../_util-fns .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 @@ -37,7 +37,7 @@ include ../_util-fns ### 让应用代码保持转译和运行 We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing - 我们要启动TypeScript编译器,它会监视文件变更,并且启动开发服务器。我们只要敲: + 我们要启动TypeScript编译器,它会监视文件变更,并启动开发服务器。我们只要敲: code-example(format="." language="bash"). npm start diff --git a/public/docs/ts/latest/tutorial/toh-pt4.jade b/public/docs/ts/latest/tutorial/toh-pt4.jade index 69d16d7adf..782e8f4cee 100644 --- a/public/docs/ts/latest/tutorial/toh-pt4.jade +++ b/public/docs/ts/latest/tutorial/toh-pt4.jade @@ -2,27 +2,42 @@ 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 @@ -41,189 +56,322 @@ include ../_util-fns .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英雄数据。 + 我们的改进点至少有两个: + 1. 定义英雄数据不应该是这个组件的任务。 + 2. 要想把这个英雄列表数据共享给其它组件和视图可不容易。 + 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 (exported class)')(format=".") ++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. -+makeExample('toh-4/ts/app/hero.service.1.ts', 'getHeroes-stub', 'hero.service.ts ( getHeroes stub)')(format=".") + + 添加一个`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. -+makeExample('toh-4/ts/app/mock-heroes.ts', null, 'mock-heroes.ts (Heroes array)') + 从`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: -+makeExample('toh-4/ts/app/app.component.1.ts', 'heroes-prop', 'app.component.ts (heroes property)')(format=".") + + 与此同时,回到我们刚刚剪切出`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`. -+makeExample('toh-4/ts/app/app.component.ts', 'hero-service-import', 'app.component.ts (import 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*: 1. we add a constructor. 1. we add to the component's `providers` metadata + 用两行代码代替用`new`时的一行: + 1. 添加一个构造函数 + 1. 添加组件的`providers`元数据 + Here's the constructor: -+makeExample('toh-4/ts/app/app.component.1.ts', 'ctor', 'app.component.ts (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. -+makeExample('toh-4/ts/app/app.component.1.ts', 'providers', 'app.component.ts (providing HeroService)') + 我们还得注册一个`HeroService` **供应商** ,以便教 *注入器* 如何创建`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`可以通过这个服务来获取英雄列表,在它组件树中的每一个子组件也可以这样做。 .l-sub-section :marked ### Services and the component tree + ### 服务和组件树 Recall that the `AppComponent` creates an instance of `HeroDetail` by virtue of the `` tag at the bottom of its template. That `HeroDetail` is a child of the `AppComponent`. + 回忆一下,`AppComponent`在它的模板底部包含了一个``标记,于是创建了一个`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`: - +makeExample('toh-4/ts/app/app.component.1.ts', 'ctor', 'hero-detail.component.ts (constructor)') + + 如果`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: