Polish server-communication.jade

This commit is contained in:
Yang Lin 2016-12-10 19:16:57 +08:00
parent 560e52768e
commit 2f5a096dea
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).
- [英雄指南范例的HTTP客户端](#http-client)
[英雄指南的 *HTTP* 客户端示例](#http-client)。
- [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).
- [处理响应对象](#extract-data)
- [处理响应对象](#extract-data)
- [Always handle errors](#error-handling).
- [总是处理错误](#error-handling)
- [总是处理错误](#error-handling)
- [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).
- [跨域请求Wikipedia例](#cors)
- [跨域请求Wikipedia例](#cors)
<ul if-docs="ts">
<ul>
<li> [Search parameters](#search-parameters).</li>
<li> [设置查询参数](#search-parameters)</li>
<li> [设置查询参数](#search-parameters)</li>
<li> [More fun with observables](#more-observables).</li>
<li> [限制搜索词输入频率](#more-observables)</li>
<li> [限制搜索词输入频率](#more-observables)。</li>
</ul>
- [Guarding against Cross-Site Request Forgery](#xsrf)
- [防止跨站请求伪造](#xsrf)
[防止跨站请求伪造](#xsrf)。
- [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.
@ -104,19 +104,19 @@ block demos-list
:marked
- [The Tour of Heroes *HTTP* client demo](#http-client).
- [英雄指南HTTP客户端](#http-client)
[英雄指南 *HTTP* 客户端](#http-client)
- [Fall back to !{_Promise}s](#promises).
- [回到使用承诺](#promises)
[回到使用承诺](#promises)
- [Cross-Origin Requests: Wikipedia example](#cors).
- [跨站请求Wikipedia例子](#cors)
[跨站请求Wikipedia 示例](#cors)
- [More fun with observables](#more-observables).
- [更多可观察对象的探索](#more-observables)
[更多可观察对象的探索](#more-observables)
:marked
The root `AppComponent` orchestrates these demos:
@ -140,12 +140,13 @@ block demos-list
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 `!{_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')
.l-sub-section
@ -154,18 +155,19 @@ block demos-list
the !{_Angular_http_library}
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
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
:marked
Read about providers in the [Dependency Injection](dependency-injection.html) page.
了解关于提供商的更多知识,参见[依赖注入](dependency-injection.html)一章
关于提供商的更多信息,见[依赖注入](dependency-injection.html)
:marked
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.
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`.
只要把它们传给我们这个根模块的`imports`数组,就可以把这些模块加入应用。
只要把它们传给根模块的`imports`数组,就可以把这些模块加入应用。
.l-sub-section
:marked
@ -192,8 +195,10 @@ block http-providers
there is a JSONP demo later in this page.
Loading its module now saves time.
我们需要HttpModule来发起HTTP调用。普通的HTTP调用并不需要用到JsonpModule
不过稍后我们就会演示对JSONP的支持所以现在就加载它免得再回来改浪费时间。
我们需要 HttpModule 来发起 HTTP 调用。
普通的 HTTP 调用并不需要用到 JsonpModule
不过稍后我们就会演示对 JSONP 的支持,
所以现在就加载它,免得再回来改浪费时间。
.l-main-section#http-client
: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.
The app uses the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`.
我们的第一个演示是《英雄指南(TOH)》教程的一个迷你版。
我们的第一个演示是《英雄指南(TOH)》[教程](../tutorial)的一个迷你版。
这个版本从服务器获取一些英雄,把它们显示在列表中,还允许我们添加新的英雄并将其保存到服务器。
借助 !{_Angular_Http}客户端,我们通过`XMLHttpRequest (XHR)`与服务器通讯。
@ -217,7 +222,7 @@ figure.image-display
:marked
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)')
:marked
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.
它使用`ngFor`来展现这个英雄列表。
列表的下方是一个输入框和一个*Add Hero*按钮,在那里,我们可以输入新英雄的名字,并把它们加到数据库中。
在`(click)`事件绑定中,我们使用[模板引用变量](template-syntax.html#ref-vars)`newHeroName`来访问这个输入框的值。
当用户点击此按钮时,我们把这个值传给组件的`addHero`方法,然后清除它,以备输入新英雄的名字。
列表的下方是一个输入框和一个 *Add Hero* 按钮,在那里,我们可以输入新英雄的名字,
并把它们加到数据库中。
在`(click)`事件绑定中,使用[模板引用变量](template-syntax.html#ref-vars)`newHeroName`来访问这个输入框的值。
当用户点击此按钮时,这个值传给组件的`addHero`方法,然后清除它,以备输入新英雄的名字。
Below the button is an area for an error message.
@ -243,6 +249,7 @@ a#HeroListComponent
### The *HeroListComponent* class
### *HeroListComponent* 类
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**.
这是一条黄金法则**总是把数据访问工作委托给一个支持服务类**。
这是一条黄金法则:**总是把数据访问工作委托给一个支持服务类**。
Although _at runtime_ the component requests heroes immediately after creation,
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)
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
:marked
This is a *best practice*.
@ -278,12 +287,14 @@ a#HeroListComponent
(especially calling a remote server) is handled in a separate method.
这是*最佳实践*。
当组件的构造函数足够简单,并且所有真实的工作(尤其是调用远端服务器)都在一个独立的方法中处理时,组件会更加容易测试和调试。
当组件的构造函数足够简单,并且所有真实的工作(尤其是调用远端服务器)
都在一个独立的方法中处理时,组件会更加容易测试和调试。
block getheroes-and-addhero
:marked
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.
To listen for events in this stream, ***subscribe*** to the `Observable`.
@ -292,12 +303,13 @@ block getheroes-and-addhero
我们可以把可观察对象`Observable`看做一个由某些“源”发布的事件流。
通过***订阅***到可观察对象`Observable`,我们监听这个流中的事件。
在这些订阅中我们指定了当Web请求生成了一个成功事件(有效载荷是英雄数据)或失败事件(有效载荷是错误对象)时该如何采取行动。
在这些订阅中,我们指定了当 Web 请求生成了一个成功事件(有效载荷是英雄数据)
或失败事件(有效载荷是错误对象)时该如何采取行动。
:marked
With a basic understanding of the component, you're ready to look inside the `HeroService`.
关于组件的浅显讲解已经结束了,我们可以到`HeroService`的内部实现中看看。
有了对组件的基本理解,我们可以到`HeroService`的内部实现中看看。
a#HeroService
.l-main-section#fetch-data
@ -315,14 +327,14 @@ a#HeroService
:marked
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)')
:marked
Notice that the !{_Angular_Http} client service is
[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')
:marked
Look closely at how to call `!{_priv}http.get`:
@ -341,8 +353,8 @@ a#HeroService
described in the appendix below.
Alternatively, you can temporarily target a JSON file by changing the endpoint URL:
一旦我们按附录中所描述的那样准备好了[内存Web API](in-mem-web-api),它**返回英雄列表。
但目前,我们只能(临时性的)使用一个JSON文件来代替这个“内存Web API”。只要修改下服务器的URL就行了
一旦我们按附录中所描述的那样准备好了[内存 Web API](in-mem-web-api),它将返回英雄列表。
但目前,你可以临时性地使用一个 JSON 文件,修改一下 URL
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
+ifDocsFor('ts')
@ -357,8 +369,8 @@ a#HeroService
<a id="rxjs"></a>
返回值可能会让我们感到意外。
对熟悉现代 JavaScript 中的异步调用方法的人来说,我们期待`get`方法返回一个[承诺 (promise)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。
我们期待链接调用`then()`方法,并从中取得英雄列表。取而代之,这里调用了一个`map()`方法。
显然,这并不是承诺(Promise)。
我们期待链接调用`then()`方法,并从中取得英雄列表。
而这里调用了一个`map()`方法,显然,它不是承诺 (Promise)。
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*.
@ -370,16 +382,18 @@ a#HeroService
## RxJS library
## RxJS库
[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.
[RxJS](https://github.com/ReactiveX/RxJS)("Reactive Extensions"的缩写)是一个被Angular认可的第三方库它实现了
[*异步可观察对象(asynchronous observable)*](https://www.youtube.com/watch?v=UHI0AzD_WfY "Rob Wormald on observables")模式。
[RxJS](https://github.com/ReactiveX/RxJS) ("Reactive Extensions" 的缩写) 是一个被 Angular 认可的第三方库,
它实现了[*异步可观察对象 (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`
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.
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 other operators are for later, in the *Wiki* example [below](#more-observables).
在`HeroService`中我们并不需要在这里导入的_全部_操作符 —— 我们只用到了`map`、`catch`和`throw`。
在`HeroService`中我们并不需要在这里导入的_全部_操作符 &mdash; 我们只用到了`map`、`catch`和`throw`。
我们稍后的 [*Wiki* 例子](#more-observables)中,还会用到其它操作符。
:marked
Finally, import `rxjs-operator` into `app.component.ts`:
@ -453,7 +467,7 @@ a#extract-data
:marked
## 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:
@ -511,7 +525,7 @@ block parse-json
:marked
### Do not return the response object
### 不要返回响应(Response)对象
### 不要返回响应对象
The `getHeroes()` method _could_ have returned the HTTP response but this wouldn't
be a best practice.
@ -538,7 +552,7 @@ block parse-json
That *something* is the [HeroListComponent](#subscribe).
`#{_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)。
@ -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
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:
@ -655,7 +670,7 @@ code-example(format="." language="javascript").
:marked
### Headers
### 请求头(Headers)
### 请求头 (headers)
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).
要理解订阅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) 的课程。
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).
Wikipedia is one such server.
有些服务器不支持CORS但支持一种老的、只读的(译注即仅支持GET)备选协议,这就是[JSONP](https://en.wikipedia.org/wiki/JSONP)。
有些服务器不支持 CORS但支持一种老的、只读的译注即仅支持 GET备选协议这就是 [JSONP](https://en.wikipedia.org/wiki/JSONP)。
Wikipedia就是一个这样的服务器。
.l-sub-section
: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 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`。
`search`的值是用户提供的用于在 Wikipedia 中查找的关键字。
另外三个参数是固定值,分别是 "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,
and calls a `search(term)` method after each `keyup` event.
组件有一个`<input>`元素,它是用来从用户获取搜索关键词的*搜索框*。
模板有一个`<input>`元素,它是用来从用户获取搜索关键词的*搜索框*。
在每次`keyup`事件被触发时,它调用`search(term)`方法。
+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 [Pipes](pipes.html) page.
`search(term)`方法委托我们的`WikipediaService`服务来完成实际操作。该服务返回的是一个字符串数组的可观察对象(`Observable<string[]>`)。
该组件的内部订阅了这个可观察对象,就像我们曾在`HeroListComponent`中所做的那样,
我们把这个可观察对象作为结果传给模板(通过`items`属性),模板中`ngFor`上的[async(异步)管道](pipes.html#async-pipe)会对这个订阅进行处理。
`search(term)`方法委托`WikipediaService`服务来完成实际操作。
该服务返回的是一个字符串数组的可观察对象 (`Observable<string[]>`)。
没有像`HeroListComponent`那样在组件内部订阅这个可观察对象,
我们把这个可观察对象作为结果传给模板(通过`items`属性),
模板中`ngFor`上的 async异步管道会对这个订阅进行处理。
关于[异步管理](pipes.html#async-pipe)的更多信息,见 [Pipes](pipes.html)。
.l-sub-section
: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.
我们通常在只读组件中使用[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`中使用这个管道,这是因为“添加新英雄”特性会把一个新创建的英雄追加到英雄列表中。
但我们不能在`HeroListComponent`中使用这个管道,这是因为`addHero()`会把一个新创建的英雄追加到英雄列表中。
:marked
## A wasteful app
@ -947,7 +965,7 @@ block wikipedia-jsonp+
The wikipedia search makes too many calls to the server.
It is inefficient, and potentially expensive on mobile devices with limited data plans.
我们这个Wikipedia搜索程序触发了过多的服务器调用(每次按键发一次)
这个 Wikipedia 搜索程序触发了过多的服务器调用。
这样效率很低,而且在流量受限的移动设备上会显得过于昂贵。
### 1. Wait for the user to stop typing
@ -960,7 +978,7 @@ block wikipedia-jsonp+
我们目前会在每次按键之后调用服务器。
但合理的方式是只在用户*停止输入*之后才发起请求。
这是它*应该*而且*即将使用*的工作方式,我们马上就重构它。
重构之后,它将这样工作:
figure.image-display
img(src='/resources/images/devguide/server-communication/wiki-2.gif' alt="Wikipedia search app (v.2)" width="250")
:marked
@ -972,7 +990,7 @@ block wikipedia-jsonp+
The application issues a search request for *angular*.
假设用户在输入框中输入了单词 *angular*,然后稍等片刻。
应用程序就会发出一个对*Angular*的搜索请求。
应用程序就会发出一个对 *angular* 的搜索请求。
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.
@ -1003,7 +1021,8 @@ block wikipedia-jsonp+
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.
即使有多个尚未返回的请求,应用程序也应该按照原始请求的顺序展示对它们的响应。如果能让*angular*的结果始终在后面返回,就不会发生这样的混乱了。
即使有多个尚未返回的请求,应用程序也应该按照原始请求的顺序展示对它们的响应。
如果能让 *angular* 的结果始终在后面返回,就不会发生这样的混乱了。
<a id="more-observables"></a>
## 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 could make changes to the `WikipediaService`, but for a better
user experience, create a copy of the `WikiComponent` instead and make it smarter.
Here's the `WikiSmartComponent` which uses the same template.
我们本可以把这些改动合并进`WikipediaService`中,但是为了更好用户体验,
转而拷贝`WikiComponent`,把它变得更智能。
我们创建一个`WikiComponent`的复本,让它变得更智能。
下面是`WikiSmartComponent`,它使用同样的模板:
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', null, 'app/wiki/wiki-smart.component.ts')
:marked
### Create a stream of search terms
### 创建一个搜索关键字组成的“(Stream)”
### 创建一个搜索关键字流
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.
@ -1037,7 +1056,7 @@ block wikipedia-jsonp+
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:
利用从RxJS库导入的`Subject``WikiSmartComponent`将搜索框的值变为一个*搜索关键词流*可观察对象:
利用从 RxJS 库导入的`Subject``WikiSmartComponent`将搜索框的值变为一个_搜索关键词流_可观察对象:
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject', 'app/wiki/wiki-smart.component.ts')
:marked
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.
前,我们每次都把搜索关键字直接传给服务,并且把模板绑定到服务返回的结果。
前,我们每次都把搜索关键字直接传给服务,并且把模板绑定到服务返回的结果。
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',
'app/wiki/wiki-smart.component.ts')(format='.')
@ -1087,8 +1106,8 @@ block wikipedia-jsonp+
re-arranges them in their original request order,
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.
@ -1116,7 +1135,7 @@ a#xsrf
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
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.
`CookieXSRFStrategy`支持常见的反 XSRF 技术,服务端发送一个随机生成的认证令牌到名为`XSRF-TOKEN`的 cookie 中。
HTTP客户端使用该令牌的值为所有请求添加一个`X-XSRF-TOKEN`页头。
HTTP 客户端使用该令牌的值为所有后续请求添加一个`X-XSRF-TOKEN`页头。
服务器接受这个 cookie 和页头,比较它们,只有在它们匹配的时候才处理请求。
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,
it uses an *in-memory web API simulator* instead.
这在*“获取”英雄数据*的场景下确实能工作,但我们还想*保存*数据。我们不能把这些改动保存到JSON文件中我们需要一个Web API服务器。
在本章中,我们不想惹上配置和维护真实服务器的那些麻烦事。
所以,我们转而使用一种*内存Web API仿真器*代替它。
这在*获取英雄数据*的场景下确实能工作,
但我们不能把这些改动保存到 JSON 文件中,因此需要一个 Web API 服务器。
因为这个演示程序中并没有一个真实的服务器,
所以,我们使用*内存 Web API 仿真器*代替它。
.l-sub-section
: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.
使用标准 Angular 提供商注册方法,`InMemoryWebApiModule`替代默认的`XHRBackend`服务并使用它自己的内存存储服务。
`forRoot`方法来自模拟的英雄数据集的*种子数据*初始化了这个内存Web API
`forRoot`方法来自模拟的英雄数据集的*种子数据*初始化了这个内存 Web API
.l-sub-section
:marked
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
Import the `InMemoryWebApiModule` _after_ the `HttpModule` to ensure that
the `XHRBackend` provider of the `InMemoryWebApiModule` supersedes all others.
在`HttpModule`之后导入`InMemoryWebApiModule`,确保`XHRBackend`的供应商`InMemoryWebApiModule`取代所有其它的供应商。
:marked
See the full source code in the <live-example></live-example>.