Merge pull request #178 from todoubaba/server-communication

Polish server-communication.jade
This commit is contained in:
Rex 2016-12-11 07:29:09 +00:00 committed by GitHub
commit 29efa06f1e
1 changed files with 210 additions and 188 deletions

View File

@ -34,57 +34,57 @@ block includes
- [The Tour of Heroes *HTTP* client demo](#http-client). - [The Tour of Heroes *HTTP* client demo](#http-client).
- [英雄指南范例的HTTP客户端](#http-client) [英雄指南的 *HTTP* 客户端示例](#http-client)。
- [Fetch data with http.get](#fetch-data). - [Fetch data with http.get](#fetch-data).
- [用http.get获取数据](#fetch-data) [用 http.get 获取数据](#fetch-data)。
<li if-docs="ts"> [RxJS library](#rxjs).</li> - [RxJS library](#rxjs).
<li if-docs="ts"> [RxJS库](#rxjs)</li> [RxJS 库](#rxjs)。
<li if-docs="ts"> [Enable RxJS operators](#enable-rxjs-operators).</li> - [Enable RxJS operators](#enable-rxjs-operators).
<li if-docs="ts"> [启用RxJS操作符](#enable-rxjs-operators)</li> [启用 RxJS 操作符](#enable-rxjs-operators)。
- [Process the response object](#extract-data). - [Process the response object](#extract-data).
- [处理响应对象](#extract-data) - [处理响应对象](#extract-data)
- [Always handle errors](#error-handling). - [Always handle errors](#error-handling).
- [总是处理错误](#error-handling) - [总是处理错误](#error-handling)
- [Send data to the server](#update). - [Send data to the server](#update).
- [把数据发送到服务器](#update) - [把数据发送到服务器](#update)
<li if-docs="ts"> [Fall back to promises](#promises).</li> - [Fall back to promises](#promises).
<li if-docs="ts"> [使用承诺(Promise)来取代可观察对象(Observable)](#promises)</li> [使用承诺 (Promise) 来取代可观察对象 (Observable)](#promises)。
- [Cross-Origin Requests: Wikipedia example](#cors). - [Cross-Origin Requests: Wikipedia example](#cors).
- [跨域请求Wikipedia例](#cors) - [跨域请求Wikipedia例](#cors)
<ul if-docs="ts"> <ul>
<li> [Search parameters](#search-parameters).</li> <li> [Search parameters](#search-parameters).</li>
<li> [设置查询参数](#search-parameters)</li> <li> [设置查询参数](#search-parameters)</li>
<li> [More fun with observables](#more-observables).</li> <li> [More fun with observables](#more-observables).</li>
<li> [限制搜索词输入频率](#more-observables)</li> <li> [限制搜索词输入频率](#more-observables)。</li>
</ul> </ul>
- [Guarding against Cross-Site Request Forgery](#xsrf) - [Guarding against Cross-Site Request Forgery](#xsrf)
- [防止跨站请求伪造](#xsrf) [防止跨站请求伪造](#xsrf)。
- [Appendix: Tour of Heroes in-memory server](#in-mem-web-api). - [Appendix: Tour of Heroes in-memory server](#in-mem-web-api).
- [附录英雄指南的内存Web API服务](#in-mem-web-api) [附录:英雄指南的内存 Web API 服务](#in-mem-web-api)
A <live-example>live example</live-example> illustrates these topics. A <live-example>live example</live-example> illustrates these topics.
@ -104,19 +104,19 @@ block demos-list
:marked :marked
- [The Tour of Heroes *HTTP* client demo](#http-client). - [The Tour of Heroes *HTTP* client demo](#http-client).
- [英雄指南HTTP客户端](#http-client) [英雄指南 *HTTP* 客户端](#http-client)
- [Fall back to !{_Promise}s](#promises). - [Fall back to !{_Promise}s](#promises).
- [回到使用承诺](#promises) [回到使用承诺](#promises)
- [Cross-Origin Requests: Wikipedia example](#cors). - [Cross-Origin Requests: Wikipedia example](#cors).
- [跨站请求Wikipedia例子](#cors) [跨站请求Wikipedia 示例](#cors)
- [More fun with observables](#more-observables). - [More fun with observables](#more-observables).
- [更多可观察对象的探索](#more-observables) [更多可观察对象的探索](#more-observables)
:marked :marked
The root `AppComponent` orchestrates these demos: The root `AppComponent` orchestrates these demos:
@ -140,12 +140,13 @@ block demos-list
First, configure the application to use server communication facilities. First, configure the application to use server communication facilities.
首先,配置应用来使用服务器对话设施。 首先,配置应用来使用服务器通讯设施。
The !{_Angular_Http} client communicates with the server using a familiar HTTP request/response protocol. The !{_Angular_Http} client communicates with the server using a familiar HTTP request/response protocol.
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}. The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
我们通过!{_Angular_Http}客户端使用熟悉的HTTP请求/回应协议与服务器通讯。`!{_Http}`客户端是!{_Angular_http_libraryCn}所提供的服务大家庭中的一员。 我们通过 !{_Angular_Http}客户端,使用熟悉的 HTTP 请求/回应协议与服务器通讯。
`!{_Http}`客户端是!{_Angular_http_libraryCn}所提供的一系列服务之一。
+ifDocsFor('ts') +ifDocsFor('ts')
.l-sub-section .l-sub-section
@ -154,18 +155,19 @@ block demos-list
the !{_Angular_http_library} the !{_Angular_http_library}
because the `systemjs.config.js` file maps to that module name. because the `systemjs.config.js` file maps to that module name.
当我们从`@angular/http`模块中导入服务时SystemJS知道该如何从!{_Angular_http_libraryCn}中加载它们,这是因为我们已经在`system.config`文件中注册过这个模块名。 当我们从`@angular/http`模块中导入服务时SystemJS 知道该如何从 !{_Angular_http_libraryCn}中加载它们,
这是因为`systemjs.config.js`文件已经注册过这个模块名。
:marked :marked
Before you can use the `!{_Http}` client, you need to register it as a service provider with the dependency injection system. Before you can use the `!{_Http}` client, you need to register it as a service provider with the dependency injection system.
要想使用`#{_Http}`客户端,我们得先通过依赖注入系统把它注册成一个服务提供商。 要想使用`#{_Http}`客户端,你需要先通过依赖注入系统把它注册成一个服务提供商。
.l-sub-section .l-sub-section
:marked :marked
Read about providers in the [Dependency Injection](dependency-injection.html) page. Read about providers in the [Dependency Injection](dependency-injection.html) page.
了解关于提供商的更多知识,参见[依赖注入](dependency-injection.html)一章 关于提供商的更多信息,见[依赖注入](dependency-injection.html)
:marked :marked
Register providers by importing other NgModules to the root NgModule in `app.module.ts`. Register providers by importing other NgModules to the root NgModule in `app.module.ts`.
@ -179,11 +181,12 @@ block http-providers
Begin by importing the necessary members. Begin by importing the necessary members.
The newcomers are the `HttpModule` and the `JsonpModule` from the !{_Angular_http_library}. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement. The newcomers are the `HttpModule` and the `JsonpModule` from the !{_Angular_http_library}. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement.
我们从导入所需的符号开始,它们中的大多数我们都熟悉了,只有`HttpModule`和`JsonpModule`是新面孔。 我们从导入所需的成员开始,它们中的大多数我们都熟悉了,只有`HttpModule`和`JsonpModule`是新面孔。
关于导入和相关术语的更多信息,见 [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 中的`import`语句。
To add these modules to the application, pass them to the `imports` array in the root `@NgModule`. To add these modules to the application, pass them to the `imports` array in the root `@NgModule`.
只要把它们传给我们这个根模块的`imports`数组,就可以把这些模块加入应用。 只要把它们传给根模块的`imports`数组,就可以把这些模块加入应用。
.l-sub-section .l-sub-section
:marked :marked
@ -192,8 +195,10 @@ block http-providers
there is a JSONP demo later in this page. there is a JSONP demo later in this page.
Loading its module now saves time. Loading its module now saves time.
我们需要HttpModule来发起HTTP调用。普通的HTTP调用并不需要用到JsonpModule 我们需要 HttpModule 来发起 HTTP 调用。
不过稍后我们就会演示对JSONP的支持所以现在就加载它免得再回来改浪费时间。 普通的 HTTP 调用并不需要用到 JsonpModule
不过稍后我们就会演示对 JSONP 的支持,
所以现在就加载它,免得再回来改浪费时间。
.l-main-section#http-client .l-main-section#http-client
:marked :marked
@ -205,7 +210,7 @@ block http-providers
This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server. This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server.
The app uses the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`. The app uses the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`.
我们的第一个演示是《英雄指南(TOH)》教程的一个迷你版。 我们的第一个演示是《英雄指南(TOH)》[教程](../tutorial)的一个迷你版。
这个版本从服务器获取一些英雄,把它们显示在列表中,还允许我们添加新的英雄并将其保存到服务器。 这个版本从服务器获取一些英雄,把它们显示在列表中,还允许我们添加新的英雄并将其保存到服务器。
借助 !{_Angular_Http}客户端,我们通过`XMLHttpRequest (XHR)`与服务器通讯。 借助 !{_Angular_Http}客户端,我们通过`XMLHttpRequest (XHR)`与服务器通讯。
@ -217,7 +222,7 @@ figure.image-display
:marked :marked
This demo has a single component, the `HeroListComponent`. Here's its template: This demo has a single component, the `HeroListComponent`. Here's its template:
这个范例是一个单一组件`HeroListComponent`,其模板如下: 这个演示是一个单一组件`HeroListComponent`,其模板如下:
+makeExample('server-communication/ts/app/toh/hero-list.component.html', null, 'app/toh/hero-list.component.html (template)') +makeExample('server-communication/ts/app/toh/hero-list.component.html', null, 'app/toh/hero-list.component.html (template)')
:marked :marked
It presents the list of heroes with an `ngFor`. It presents the list of heroes with an `ngFor`.
@ -229,9 +234,10 @@ figure.image-display
the event binding clears it to make it ready for a new hero name. the event binding clears it to make it ready for a new hero name.
它使用`ngFor`来展现这个英雄列表。 它使用`ngFor`来展现这个英雄列表。
列表的下方是一个输入框和一个*Add Hero*按钮,在那里,我们可以输入新英雄的名字,并把它们加到数据库中。 列表的下方是一个输入框和一个 *Add Hero* 按钮,在那里,我们可以输入新英雄的名字,
在`(click)`事件绑定中,我们使用[模板引用变量](template-syntax.html#ref-vars)`newHeroName`来访问这个输入框的值。 并把它们加到数据库中。
当用户点击此按钮时,我们把这个值传给组件的`addHero`方法,然后清除它,以备输入新英雄的名字。 在`(click)`事件绑定中,使用[模板引用变量](template-syntax.html#ref-vars)`newHeroName`来访问这个输入框的值。
当用户点击此按钮时,这个值传给组件的`addHero`方法,然后清除它,以备输入新英雄的名字。
Below the button is an area for an error message. Below the button is an area for an error message.
@ -243,6 +249,7 @@ a#HeroListComponent
### The *HeroListComponent* class ### The *HeroListComponent* class
### *HeroListComponent* 类 ### *HeroListComponent* 类
Here's the component class: Here's the component class:
下面是这个组件类: 下面是这个组件类:
@ -262,15 +269,17 @@ a#HeroListComponent
This is a golden rule: **always delegate data access to a supporting service class**. This is a golden rule: **always delegate data access to a supporting service class**.
这是一条黄金法则**总是把数据访问工作委托给一个支持服务类**。 这是一条黄金法则:**总是把数据访问工作委托给一个支持服务类**。
Although _at runtime_ the component requests heroes immediately after creation, Although _at runtime_ the component requests heroes immediately after creation,
you **don't** call the service's `get` method in the component's constructor. you **don't** call the service's `get` method in the component's constructor.
Instead, call it inside the `ngOnInit` [lifecycle hook](lifecycle-hooks.html) Instead, call it inside the `ngOnInit` [lifecycle hook](lifecycle-hooks.html)
and rely on Angular to call `ngOnInit` when it instantiates this component. and rely on Angular to call `ngOnInit` when it instantiates this component.
虽然_在运行期间_组件会在创建之后立刻请求这些英雄数据但我们**不**在组件的构造函数中调用此服务的`get`方法。 虽然_在运行期间_组件会在创建之后立刻请求这些英雄数据
而是在`ngOnInit`[生命周期钩子](lifecycle-hooks.html)中调用它Angular会在初始化该组件时调用`ngOnInit`方法。 但我们**不**在组件的构造函数中调用此服务的`get`方法。
而是在`ngOnInit`[生命周期钩子](lifecycle-hooks.html)中调用它,
Angular 会在初始化该组件时调用`ngOnInit`方法。
.l-sub-section .l-sub-section
:marked :marked
This is a *best practice*. This is a *best practice*.
@ -278,12 +287,14 @@ a#HeroListComponent
(especially calling a remote server) is handled in a separate method. (especially calling a remote server) is handled in a separate method.
这是*最佳实践*。 这是*最佳实践*。
当组件的构造函数足够简单,并且所有真实的工作(尤其是调用远端服务器)都在一个独立的方法中处理时,组件会更加容易测试和调试。 当组件的构造函数足够简单,并且所有真实的工作(尤其是调用远端服务器)
都在一个独立的方法中处理时,组件会更加容易测试和调试。
block getheroes-and-addhero block getheroes-and-addhero
:marked :marked
The service's `getHeroes()` and `addHero()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server. The service's `getHeroes()` and `addHero()` methods return an `Observable` of hero data that the !{_Angular_Http} client fetched from the server.
服务的`getHeroes()`和`addHero()`方法返回一个英雄数据的可观察对象(`Observable`),这些数据是由!{_Angular_Http}从服务器上获取的。 服务的`getHeroes()`和`addHero()`方法返回一个英雄数据的可观察对象 (`Observable`)
这些数据是由 !{_Angular_Http}从服务器上获取的。
Think of an `Observable` as a stream of events published by some source. Think of an `Observable` as a stream of events published by some source.
To listen for events in this stream, ***subscribe*** to the `Observable`. To listen for events in this stream, ***subscribe*** to the `Observable`.
@ -292,12 +303,13 @@ block getheroes-and-addhero
我们可以把可观察对象`Observable`看做一个由某些“源”发布的事件流。 我们可以把可观察对象`Observable`看做一个由某些“源”发布的事件流。
通过***订阅***到可观察对象`Observable`,我们监听这个流中的事件。 通过***订阅***到可观察对象`Observable`,我们监听这个流中的事件。
在这些订阅中我们指定了当Web请求生成了一个成功事件(有效载荷是英雄数据)或失败事件(有效载荷是错误对象)时该如何采取行动。 在这些订阅中,我们指定了当 Web 请求生成了一个成功事件(有效载荷是英雄数据)
或失败事件(有效载荷是错误对象)时该如何采取行动。
:marked :marked
With a basic understanding of the component, you're ready to look inside the `HeroService`. With a basic understanding of the component, you're ready to look inside the `HeroService`.
关于组件的浅显讲解已经结束了,我们可以到`HeroService`的内部实现中看看。 有了对组件的基本理解,我们可以到`HeroService`的内部实现中看看。
a#HeroService a#HeroService
.l-main-section#fetch-data .l-main-section#fetch-data
@ -315,14 +327,14 @@ a#HeroService
:marked :marked
You can revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service: You can revise that `HeroService` to get the heroes from the server using the !{_Angular_Http} client service:
在本章中,我们会修改`HeroService`,改用“!{_Angular_Http}客户端”服务来从服务器上获取英雄列表: 在本章中,我们会修改`HeroService`,改用 !{_Angular_Http}客户端服务来从服务器上获取英雄列表:
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts (revised)') +makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts (revised)')
:marked :marked
Notice that the !{_Angular_Http} client service is Notice that the !{_Angular_Http} client service is
[injected](dependency-injection.html) into the `HeroService` constructor. [injected](dependency-injection.html) into the `HeroService` constructor.
注意,这个“!{_Angular_Http}客户端”服务[被注入](dependency-injection.html)到了`HeroService`的构造函数中。 注意,这个 !{_Angular_Http}客户端服务[被注入](dependency-injection.html)到了`HeroService`的构造函数中。
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'ctor') +makeExample('server-communication/ts/app/toh/hero.service.ts', 'ctor')
:marked :marked
Look closely at how to call `!{_priv}http.get`: Look closely at how to call `!{_priv}http.get`:
@ -341,8 +353,8 @@ a#HeroService
described in the appendix below. described in the appendix below.
Alternatively, you can temporarily target a JSON file by changing the endpoint URL: Alternatively, you can temporarily target a JSON file by changing the endpoint URL:
一旦我们按附录中所描述的那样准备好了[内存Web API](in-mem-web-api),它**返回英雄列表。 一旦我们按附录中所描述的那样准备好了[内存 Web API](in-mem-web-api),它将返回英雄列表。
但目前,我们只能(临时性的)使用一个JSON文件来代替这个“内存Web API”。只要修改下服务器的URL就行了 但目前,你可以临时性地使用一个 JSON 文件,修改一下 URL
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
+ifDocsFor('ts') +ifDocsFor('ts')
@ -357,8 +369,8 @@ a#HeroService
<a id="rxjs"></a> <a id="rxjs"></a>
返回值可能会让我们感到意外。 返回值可能会让我们感到意外。
对熟悉现代 JavaScript 中的异步调用方法的人来说,我们期待`get`方法返回一个[承诺 (promise)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。 对熟悉现代 JavaScript 中的异步调用方法的人来说,我们期待`get`方法返回一个[承诺 (promise)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。
我们期待链接调用`then()`方法,并从中取得英雄列表。取而代之,这里调用了一个`map()`方法。 我们期待链接调用`then()`方法,并从中取得英雄列表。
显然,这并不是承诺(Promise)。 而这里调用了一个`map()`方法,显然,它不是承诺 (Promise)。
In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable<Response>`) from the RxJS library
and `map` is one of the RxJS *operators*. and `map` is one of the RxJS *operators*.
@ -370,16 +382,18 @@ a#HeroService
## RxJS library ## RxJS library
## RxJS库 ## RxJS库
[RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions") is a 3rd party library, endorsed by Angular, [RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions") is a 3rd party library, endorsed by Angular,
that implements the [*asynchronous observable*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables") pattern. that implements the [*asynchronous observable*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables") pattern.
[RxJS](https://github.com/ReactiveX/RxJS)("Reactive Extensions"的缩写)是一个被Angular认可的第三方库它实现了 [RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions" 的缩写) 是一个被 Angular 认可的第三方库,
[*异步可观察对象(asynchronous observable)*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables")模式。 它实现了[*异步可观察对象 (asynchronous observable)*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables") 模式。
All of the Developer Guide samples have installed the RxJS npm package and loaded via `system.js` All of the Developer Guide samples have installed the RxJS npm package and loaded via `system.js`
because observables are used widely in Angular applications. because observables are used widely in Angular applications.
本开发指南中的所有例子都安装了RxJS的npm包而且都被`system.js`加载过了。这是因为可观察对象在Angular应用中使用非常广泛。 开发指南中的所有例子都安装了 RxJS 的 npm 包,而且都被`system.js`加载过了。
这是因为可观察对象在 Angular 应用中使用非常广泛。
The app needs it when working with the HTTP client. The app needs it when working with the HTTP client.
Additionally, you must take a critical extra step to make RxJS observables usable. Additionally, you must take a critical extra step to make RxJS observables usable.
@ -435,7 +449,7 @@ a#HeroService
The app doesn't need _all_ of these particular operators in the `HeroService` &mdash; just `map`, `catch` and `throw`. The app doesn't need _all_ of these particular operators in the `HeroService` &mdash; just `map`, `catch` and `throw`.
The other operators are for later, in the *Wiki* example [below](#more-observables). The other operators are for later, in the *Wiki* example [below](#more-observables).
在`HeroService`中我们并不需要在这里导入的_全部_操作符 —— 我们只用到了`map`、`catch`和`throw`。 在`HeroService`中我们并不需要在这里导入的_全部_操作符 &mdash; 我们只用到了`map`、`catch`和`throw`。
我们稍后的 [*Wiki* 例子](#more-observables)中,还会用到其它操作符。 我们稍后的 [*Wiki* 例子](#more-observables)中,还会用到其它操作符。
:marked :marked
Finally, import `rxjs-operator` into `app.component.ts`: Finally, import `rxjs-operator` into `app.component.ts`:
@ -453,7 +467,7 @@ a#extract-data
:marked :marked
## Process the response object ## Process the response object
## 处理Response响应对象 ## 处理响应对象
Remember that the `getHeroes()` method used an `!{_priv}extractData` helper method to map the `!{_priv}http.get` response object to heroes: Remember that the `getHeroes()` method used an `!{_priv}extractData` helper method to map the `!{_priv}http.get` response object to heroes:
@ -511,7 +525,7 @@ block parse-json
:marked :marked
### Do not return the response object ### Do not return the response object
### 不要返回响应(Response)对象 ### 不要返回响应对象
The `getHeroes()` method _could_ have returned the HTTP response but this wouldn't The `getHeroes()` method _could_ have returned the HTTP response but this wouldn't
be a best practice. be a best practice.
@ -538,7 +552,7 @@ block parse-json
That *something* is the [HeroListComponent](#subscribe). That *something* is the [HeroListComponent](#subscribe).
`#{_priv}http.get`**仍然没有发送请求!**这是因为可观察对象是 `#{_priv}http.get`**仍然没有发送请求!**这是因为可观察对象是
[*冷的*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables) [*冷的*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables)
也就是说,只有当某人*订阅*了这个可观察对象时,这个请求才会被发出。 也就是说,只有当某人*订阅*了这个可观察对象时,这个请求才会被发出。
这个场景中的*某人*就是[HeroListComponent](#subscribe)。 这个场景中的*某人*就是[HeroListComponent](#subscribe)。
@ -642,7 +656,8 @@ code-example(format="." language="javascript").
of the new hero including its generated id. The hero arrives tucked inside a response object of the new hero including its generated id. The hero arrives tucked inside a response object
with its own `data` property. with its own `data` property.
服务器将生成`id`,并且返回新英雄的完整`JSON`形式包括这个生成的id。该英雄的数据被塞进一个响应对象的`data`属性中。 服务器将生成`id`,并且返回新英雄的完整`JSON`形式,包括这个生成的 id。
该英雄的数据被塞进一个响应对象的`data`属性中。
Now that you know how the API works, implement `addHero()`as follows: Now that you know how the API works, implement `addHero()`as follows:
@ -655,7 +670,7 @@ code-example(format="." language="javascript").
:marked :marked
### Headers ### Headers
### 请求头(Headers) ### 请求头 (headers)
In the `headers` object, the `Content-Type` specifies that the body represents JSON. In the `headers` object, the `Content-Type` specifies that the body represents JSON.
@ -774,7 +789,7 @@ block hero-list-comp-add-hero
To understand the implications and consequences of subscriptions, watch [Ben Lesh's talk on observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE) or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises). To understand the implications and consequences of subscriptions, watch [Ben Lesh's talk on observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE) or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises).
要理解订阅Subscription的实现和效果,请看[Ben Lesh关于可观察对象的演讲](https://www.youtube.com/watch?v=3LKMwkuK0ZE) 要理解订阅的实现和效果,请看 [Ben Lesh 关于可观察对象的演讲](https://www.youtube.com/watch?v=3LKMwkuK0ZE)
或者他在 [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises) 的课程。 或者他在 [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises) 的课程。
h2#cors Cross-Origin Requests: Wikipedia example h2#cors Cross-Origin Requests: Wikipedia example
@ -808,7 +823,7 @@ h2#cors 跨域请求Wikipedia范例
Some servers do not support CORS but do support an older, read-only alternative called [JSONP](https://en.wikipedia.org/wiki/JSONP). Some servers do not support CORS but do support an older, read-only alternative called [JSONP](https://en.wikipedia.org/wiki/JSONP).
Wikipedia is one such server. Wikipedia is one such server.
有些服务器不支持CORS但支持一种老的、只读的(译注即仅支持GET)备选协议,这就是[JSONP](https://en.wikipedia.org/wiki/JSONP)。 有些服务器不支持 CORS但支持一种老的、只读的译注即仅支持 GET备选协议这就是 [JSONP](https://en.wikipedia.org/wiki/JSONP)。
Wikipedia就是一个这样的服务器。 Wikipedia就是一个这样的服务器。
.l-sub-section .l-sub-section
:marked :marked
@ -863,7 +878,7 @@ block wikipedia-jsonp+
The value of the `search` key is the user-supplied search term to find in Wikipedia. The value of the `search` key is the user-supplied search term to find in Wikipedia.
The other three are the fixed values "opensearch", "json", and "JSONP_CALLBACK" respectively. The other three are the fixed values "opensearch", "json", and "JSONP_CALLBACK" respectively.
[Wikipedia 的 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch)期待在所请求的URL中带四个查询参数(键/值对格式) [Wikipedia 的 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch) 期待在所请求的 URL 中带四个查询参数(键/值对格式)
这些键 (key) 分别是`search`、`action`、`format`和`callback`。 这些键 (key) 分别是`search`、`action`、`format`和`callback`。
`search`的值是用户提供的用于在 Wikipedia 中查找的关键字。 `search`的值是用户提供的用于在 Wikipedia 中查找的关键字。
另外三个参数是固定值,分别是 "opensearch"、"json" 和 "JSONP_CALLBACK"。 另外三个参数是固定值,分别是 "opensearch"、"json" 和 "JSONP_CALLBACK"。
@ -914,7 +929,7 @@ block wikipedia-jsonp+
The template presents an `<input>` element *search box* to gather search terms from the user, The template presents an `<input>` element *search box* to gather search terms from the user,
and calls a `search(term)` method after each `keyup` event. and calls a `search(term)` method after each `keyup` event.
组件有一个`<input>`元素,它是用来从用户获取搜索关键词的*搜索框*。 模板有一个`<input>`元素,它是用来从用户获取搜索关键词的*搜索框*。
在每次`keyup`事件被触发时,它调用`search(term)`方法。 在每次`keyup`事件被触发时,它调用`search(term)`方法。
+makeExample('server-communication/ts/app/wiki/wiki.component.html', 'keyup', 'wiki/wiki.component.html')(format='.') +makeExample('server-communication/ts/app/wiki/wiki.component.html', 'keyup', 'wiki/wiki.component.html')(format='.')
@ -926,18 +941,21 @@ block wikipedia-jsonp+
in the `ngFor` handles the subscription. Read more about [async pipes](pipes.html#async-pipe) in the `ngFor` handles the subscription. Read more about [async pipes](pipes.html#async-pipe)
in the [Pipes](pipes.html) page. in the [Pipes](pipes.html) page.
`search(term)`方法委托我们的`WikipediaService`服务来完成实际操作。该服务返回的是一个字符串数组的可观察对象(`Observable<string[]>`)。 `search(term)`方法委托`WikipediaService`服务来完成实际操作。
该组件的内部订阅了这个可观察对象,就像我们曾在`HeroListComponent`中所做的那样, 该服务返回的是一个字符串数组的可观察对象 (`Observable<string[]>`)。
我们把这个可观察对象作为结果传给模板(通过`items`属性),模板中`ngFor`上的[async(异步)管道](pipes.html#async-pipe)会对这个订阅进行处理。 没有像`HeroListComponent`那样在组件内部订阅这个可观察对象,
我们把这个可观察对象作为结果传给模板(通过`items`属性),
模板中`ngFor`上的 async异步管道会对这个订阅进行处理。
关于[异步管理](pipes.html#async-pipe)的更多信息,见 [Pipes](pipes.html)。
.l-sub-section .l-sub-section
:marked :marked
The [async pipe](pipes.html#async-pipe) is a good choice in read-only components where the component has no need to interact with the data. The [async pipe](pipes.html#async-pipe) is a good choice in read-only components where the component has no need to interact with the data.
我们通常在只读组件中使用[async管道](pipes.html#async-pipe),这种组件不需要与数据进行互动。 我们通常在只读组件中使用[异步管道](pipes.html#async-pipe),这种组件不需要与数据进行互动。
`HeroListComponent` can't use the pipe because `addHero()` pushes newly created heroes into the list. `HeroListComponent` can't use the pipe because `addHero()` pushes newly created heroes into the list.
但我们不能在`HeroListComponent`中使用这个管道,这是因为“添加新英雄”特性会把一个新创建的英雄追加到英雄列表中。 但我们不能在`HeroListComponent`中使用这个管道,这是因为`addHero()`会把一个新创建的英雄追加到英雄列表中。
:marked :marked
## A wasteful app ## A wasteful app
@ -947,7 +965,7 @@ block wikipedia-jsonp+
The wikipedia search makes too many calls to the server. The wikipedia search makes too many calls to the server.
It is inefficient, and potentially expensive on mobile devices with limited data plans. It is inefficient, and potentially expensive on mobile devices with limited data plans.
我们这个Wikipedia搜索程序触发了过多的服务器调用(每次按键发一次) 这个 Wikipedia 搜索程序触发了过多的服务器调用。
这样效率很低,而且在流量受限的移动设备上会显得过于昂贵。 这样效率很低,而且在流量受限的移动设备上会显得过于昂贵。
### 1. Wait for the user to stop typing ### 1. Wait for the user to stop typing
@ -960,7 +978,7 @@ block wikipedia-jsonp+
我们目前会在每次按键之后调用服务器。 我们目前会在每次按键之后调用服务器。
但合理的方式是只在用户*停止输入*之后才发起请求。 但合理的方式是只在用户*停止输入*之后才发起请求。
这是它*应该*而且*即将使用*的工作方式,我们马上就重构它。 重构之后,它将这样工作:
figure.image-display figure.image-display
img(src='/resources/images/devguide/server-communication/wiki-2.gif' alt="Wikipedia search app (v.2)" width="250") img(src='/resources/images/devguide/server-communication/wiki-2.gif' alt="Wikipedia search app (v.2)" width="250")
:marked :marked
@ -972,7 +990,7 @@ block wikipedia-jsonp+
The application issues a search request for *angular*. The application issues a search request for *angular*.
假设用户在输入框中输入了单词 *angular*,然后稍等片刻。 假设用户在输入框中输入了单词 *angular*,然后稍等片刻。
应用程序就会发出一个对*Angular*的搜索请求。 应用程序就会发出一个对 *angular* 的搜索请求。
Then the user backspaces over the last three letters, *lar*, and immediately re-types *lar* before pausing once more. Then the user backspaces over the last three letters, *lar*, and immediately re-types *lar* before pausing once more.
The search term is still _angular_. The app shouldn't make another request. The search term is still _angular_. The app shouldn't make another request.
@ -1003,7 +1021,8 @@ block wikipedia-jsonp+
When there are multiple requests in-flight, the app should present the responses When there are multiple requests in-flight, the app should present the responses
in the original request order. That won't happen if *angular* results arrive last. in the original request order. That won't happen if *angular* results arrive last.
即使有多个尚未返回的请求,应用程序也应该按照原始请求的顺序展示对它们的响应。如果能让*angular*的结果始终在后面返回,就不会发生这样的混乱了。 即使有多个尚未返回的请求,应用程序也应该按照原始请求的顺序展示对它们的响应。
如果能让 *angular* 的结果始终在后面返回,就不会发生这样的混乱了。
<a id="more-observables"></a> <a id="more-observables"></a>
## More fun with observables ## More fun with observables
@ -1012,21 +1031,21 @@ block wikipedia-jsonp+
You can address these problems and improve the app with the help of some nifty observable operators. You can address these problems and improve the app with the help of some nifty observable operators.
借助一些漂亮的可观察对象操作符,我们可以解决这些问题,并提升我们的应用程序。 借助一些漂亮的可观察对象操作符,我们可以解决这些问题,并改进我们的应用程序。
You could make changes to the `WikipediaService`, but for a better You could make changes to the `WikipediaService`, but for a better
user experience, create a copy of the `WikiComponent` instead and make it smarter. user experience, create a copy of the `WikiComponent` instead and make it smarter.
Here's the `WikiSmartComponent` which uses the same template. Here's the `WikiSmartComponent` which uses the same template.
我们本可以把这些改动合并进`WikipediaService`中,但是为了更好用户体验, 我们本可以把这些改动合并进`WikipediaService`中,但是为了更好用户体验,
转而拷贝`WikiComponent`,把它变得更智能。 我们创建一个`WikiComponent`的复本,让它变得更智能。
下面是`WikiSmartComponent`,它使用同样的模板: 下面是`WikiSmartComponent`,它使用同样的模板:
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', null, 'app/wiki/wiki-smart.component.ts') +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', null, 'app/wiki/wiki-smart.component.ts')
:marked :marked
### Create a stream of search terms ### Create a stream of search terms
### 创建一个搜索关键字组成的“(Stream)” ### 创建一个搜索关键字流
The template still binds to the search box `keyup` event and passes the complete search box value The template still binds to the search box `keyup` event and passes the complete search box value
into the component's `search` method after every user keystroke. into the component's `search` method after every user keystroke.
@ -1037,7 +1056,7 @@ block wikipedia-jsonp+
The `WikiSmartComponent` turns the search box values into an observable _stream of search terms_ The `WikiSmartComponent` turns the search box values into an observable _stream of search terms_
with the help of a `Subject` which you import from the RxJS observable library: with the help of a `Subject` which you import from the RxJS observable library:
利用从RxJS库导入的`Subject``WikiSmartComponent`将搜索框的值变为一个*搜索关键词流*可观察对象: 利用从 RxJS 库导入的`Subject``WikiSmartComponent`将搜索框的值变为一个_搜索关键词流_可观察对象:
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject', 'app/wiki/wiki-smart.component.ts') +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject', 'app/wiki/wiki-smart.component.ts')
:marked :marked
The component creates a `searchTermStream` as a `Subject` of type `string`. The component creates a `searchTermStream` as a `Subject` of type `string`.
@ -1055,11 +1074,11 @@ block wikipedia-jsonp+
Earlier, you passed each search term directly to the service and bound the template to the service results. Earlier, you passed each search term directly to the service and bound the template to the service results.
前,我们每次都把搜索关键字直接传给服务,并且把模板绑定到服务返回的结果。 前,我们每次都把搜索关键字直接传给服务,并且把模板绑定到服务返回的结果。
Now you listen to the *stream of search terms*, manipulating the stream before it reaches the `WikipediaService`. Now you listen to the *stream of search terms*, manipulating the stream before it reaches the `WikipediaService`.
而现在我们在监听*关键字组成的流*,并在把它传给`WikipediaService`之前操作这个流。 而现在我们在监听*搜索关键字流*,并在把它传给`WikipediaService`之前操作这个流。
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators', +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators',
'app/wiki/wiki-smart.component.ts')(format='.') 'app/wiki/wiki-smart.component.ts')(format='.')
@ -1087,8 +1106,8 @@ block wikipedia-jsonp+
re-arranges them in their original request order, re-arranges them in their original request order,
and delivers to subscribers only the most recent search results. and delivers to subscribers only the most recent search results.
[switchMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)(以前叫`flatMapLatest`) [switchMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)(以前叫`flatMapLatest`
返回一个新的可观察对象,它组合了所有这些“可观察的字符串数组”,重新按照它们的原始请求顺序进行排列,然后把最近的一个搜索结果交付给调用者。 返回一个新的可观察对象,它组合了所有这些“可观察的字符串数组”,重新按照它们的原始请求顺序进行排列,然后把最近的一个搜索结果交付给订阅者。
The displayed list of search results stays in sync with the user's sequence of search terms. The displayed list of search results stays in sync with the user's sequence of search terms.
@ -1116,7 +1135,7 @@ a#xsrf
Angular's `Http` client does its part by applying a default `CookieXSRFStrategy` automatically to all requests. Angular's `Http` client does its part by applying a default `CookieXSRFStrategy` automatically to all requests.
客户端和服务器必须合作来抵挡这种攻击。 客户端和服务器必须合作来抵挡这种攻击。
Angular的`http`客户端自动使用它默认的`XSRFStrategy`来完成客户端的任务。 Angular 的`http`客户端自动使用它默认的`CookieXSRFStrategy`来完成客户端的任务。
The `CookieXSRFStrategy` supports a common anti-XSRF technique in which the server sends a randomly The `CookieXSRFStrategy` supports a common anti-XSRF technique in which the server sends a randomly
generated authentication token in a cookie named `XSRF-TOKEN`. generated authentication token in a cookie named `XSRF-TOKEN`.
@ -1124,7 +1143,7 @@ a#xsrf
The server receives both the cookie and the header, compares them, and processes the request only if the cookie and header match. The server receives both the cookie and the header, compares them, and processes the request only if the cookie and header match.
`CookieXSRFStrategy`支持常见的反 XSRF 技术,服务端发送一个随机生成的认证令牌到名为`XSRF-TOKEN`的 cookie 中。 `CookieXSRFStrategy`支持常见的反 XSRF 技术,服务端发送一个随机生成的认证令牌到名为`XSRF-TOKEN`的 cookie 中。
HTTP客户端使用该令牌的值为所有请求添加一个`X-XSRF-TOKEN`页头。 HTTP 客户端使用该令牌的值为所有后续请求添加一个`X-XSRF-TOKEN`页头。
服务器接受这个 cookie 和页头,比较它们,只有在它们匹配的时候才处理请求。 服务器接受这个 cookie 和页头,比较它们,只有在它们匹配的时候才处理请求。
See the [XSRF topic on the Security page](security.html#xsrf) for more information about XSRF and Angular's `XSRFStrategy` counter measures. See the [XSRF topic on the Security page](security.html#xsrf) for more information about XSRF and Angular's `XSRFStrategy` counter measures.
@ -1165,9 +1184,10 @@ a#in-mem-web-api
Because there isn't a real server for this demo, Because there isn't a real server for this demo,
it uses an *in-memory web API simulator* instead. it uses an *in-memory web API simulator* instead.
这在*“获取”英雄数据*的场景下确实能工作,但我们还想*保存*数据。我们不能把这些改动保存到JSON文件中我们需要一个Web API服务器。 这在*获取英雄数据*的场景下确实能工作,
在本章中,我们不想惹上配置和维护真实服务器的那些麻烦事。 但我们不能把这些改动保存到 JSON 文件中,因此需要一个 Web API 服务器。
所以,我们转而使用一种*内存Web API仿真器*代替它。 因为这个演示程序中并没有一个真实的服务器,
所以,我们使用*内存 Web API 仿真器*代替它。
.l-sub-section .l-sub-section
:marked :marked
@ -1221,7 +1241,7 @@ block redirect-to-web-api
At the same time, the `forRoot` method initializes the in-memory web API with the *seed data* from the mock hero dataset. At the same time, the `forRoot` method initializes the in-memory web API with the *seed data* from the mock hero dataset.
使用标准 Angular 提供商注册方法,`InMemoryWebApiModule`替代默认的`XHRBackend`服务并使用它自己的内存存储服务。 使用标准 Angular 提供商注册方法,`InMemoryWebApiModule`替代默认的`XHRBackend`服务并使用它自己的内存存储服务。
`forRoot`方法来自模拟的英雄数据集的*种子数据*初始化了这个内存Web API `forRoot`方法来自模拟的英雄数据集的*种子数据*初始化了这个内存 Web API
.l-sub-section .l-sub-section
:marked :marked
The `forRoot` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_, The `forRoot` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_,
@ -1239,6 +1259,8 @@ block redirect-to-web-api
:marked :marked
Import the `InMemoryWebApiModule` _after_ the `HttpModule` to ensure that Import the `InMemoryWebApiModule` _after_ the `HttpModule` to ensure that
the `XHRBackend` provider of the `InMemoryWebApiModule` supersedes all others. the `XHRBackend` provider of the `InMemoryWebApiModule` supersedes all others.
在`HttpModule`之后导入`InMemoryWebApiModule`,确保`XHRBackend`的供应商`InMemoryWebApiModule`取代所有其它的供应商。
:marked :marked
See the full source code in the <live-example></live-example>. See the full source code in the <live-example></live-example>.