开发指南-HTTP客户端 翻译完毕

This commit is contained in:
Zhicheng Wang 2016-05-25 21:17:59 +08:00
parent 33aed368c9
commit 79eddc2553
1 changed files with 195 additions and 0 deletions

View File

@ -662,20 +662,33 @@ block hero-list-comp-add-hero
:marked :marked
Back in the `HeroListComponent`, we see that *its* `addHero()` method subscribes to the observable returned by the *service's* `addHero()` method. Back in the `HeroListComponent`, we see that *its* `addHero()` method subscribes to the observable returned by the *service's* `addHero()` method.
When the data, arrive it pushes the new hero object into its `heroes` array for presentation to the user. When the data, arrive it pushes the new hero object into its `heroes` array for presentation to the user.
回到`HeroListComponent`,我们看到*该组件的*`addHero()`方法中订阅了这个由*服务中*的`addHero()`方法返回的可观察对象。
当有数据到来时,它就会把这个新的英雄对象追加(push)到`heroes`数组中,以展现给用户。
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'addHero', 'app/toh/hero-list.component.ts (addHero)')(format=".") +makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'addHero', 'app/toh/hero-list.component.ts (addHero)')(format=".")
block promises block promises
h2#promises Fall back to Promises h2#promises Fall back to Promises
h2#promises 退化为承诺(Promise)
:marked :marked
Although the Angular `http` client API returns an `Observable<Response>` we can turn it into a Although the Angular `http` client API returns an `Observable<Response>` we can turn it into a
[Promise<Response>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) if we prefer. [Promise<Response>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) if we prefer.
It's easy to do and a promise-based version looks much like the observable-based version in simple cases. It's easy to do and a promise-based version looks much like the observable-based version in simple cases.
虽然Angular的`http`客户端API返回的是`Observable<Response>`类型的对象,但我们也可以把它转成
[Promise<Response>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。
这很容易,并且在简单的场景中,一个基于承诺(Promise)的版本看起来很像基于可观察对象(Observable)的版本。
.l-sub-section .l-sub-section
:marked :marked
While promises may be more familiar, observables have many advantages. While promises may be more familiar, observables have many advantages.
Don't rush to promises until you give observables a chance. Don't rush to promises until you give observables a chance.
可能“承诺”看起来更熟悉一些,但“可观察对象”有很多优越之处。
不要匆匆忙忙的就决定用“承诺”,你值得给“可观察对象”一次机会。
:marked :marked
Let's rewrite the `HeroService` using promises , highlighting just the parts that are different. Let's rewrite the `HeroService` using promises , highlighting just the parts that are different.
我们来使用承诺重写`HeroService`,要特别注意那些不同的部分。
+makeTabs( +makeTabs(
'server-communication/ts/app/toh/hero.service.promise.ts,server-communication/ts/app/toh/hero.service.ts', 'server-communication/ts/app/toh/hero.service.promise.ts,server-communication/ts/app/toh/hero.service.ts',
'methods, methods', 'methods, methods',
@ -683,16 +696,27 @@ block promises
:marked :marked
Converting from an observable to a promise is as simple as calling `toPromise(success, fail)`. Converting from an observable to a promise is as simple as calling `toPromise(success, fail)`.
把可观察对象转变成承诺是很简单的,只要调用`toPromise(success, fail)`就行了。
We move the observable's `map` callback to the first *success* parameter and its `catch` callback to the second *fail* parameter We move the observable's `map` callback to the first *success* parameter and its `catch` callback to the second *fail* parameter
and we're done! and we're done!
Or we can follow the promise `then.catch` pattern as we do in the second `addHero` example. Or we can follow the promise `then.catch` pattern as we do in the second `addHero` example.
我们把可观察对象的`map`回调移到第一个*success*参数中,而它的`catch`回调移到第二个*fail*参数中,这就算完工了。
或者我们可以像第二个`addHero`例子中那样使用承诺的`then.catch`模式。
Our `errorHandler` forwards an error message as a failed promise instead of a failed Observable. Our `errorHandler` forwards an error message as a failed promise instead of a failed Observable.
我们的`errorHandler`也改用了一个失败的承诺,而不再是失败的可观察对象。
The diagnostic *log to console* is just one more `then` in the promise chain. The diagnostic *log to console* is just one more `then` in the promise chain.
把诊断信息*记录到控制台*也只是在承诺的处理链中多了一个`then`而已。
We have to adjust the calling component to expect a `Promise` instead of an `Observable`. We have to adjust the calling component to expect a `Promise` instead of an `Observable`.
为了用`Observable`代替`Promise`,我们还得对调用方组件进行调整。
+makeTabs( +makeTabs(
'server-communication/ts/app/toh/hero-list.component.promise.ts, server-communication/ts/app/toh/hero-list.component.ts', 'server-communication/ts/app/toh/hero-list.component.promise.ts, server-communication/ts/app/toh/hero-list.component.ts',
'methods, methods', 'methods, methods',
@ -700,45 +724,78 @@ block promises
:marked :marked
The only obvious difference is that we call `then` on the returned promise instead of `subscribe`. The only obvious difference is that we call `then` on the returned promise instead of `subscribe`.
We give both methods the same functional arguments. We give both methods the same functional arguments.
唯一一个比较明显的不同点是我们调用这个返回的承诺的`then`方法,而不再是`subscribe`。
而且我们给了这两个方法完全相同的调用参数。
.l-sub-section .l-sub-section
:marked :marked
The less obvious but critical difference is that these two methods return very different results! The less obvious but critical difference is that these two methods return very different results!
细微却又关键的不同点是,这两个方法返回了非常不同的结果!
The promise-based `then` returns another promise. We can keep chaining more `then` and `catch` calls, getting a new promise each time. The promise-based `then` returns another promise. We can keep chaining more `then` and `catch` calls, getting a new promise each time.
基于承诺的`then`返回了另一个承诺。我们可以链式调用多个`then`和`catch`方法,每次都返回一个新的承诺。
The `subscribe` method returns a `Subscription`. A `Subscription` is not another `Observable`. The `subscribe` method returns a `Subscription`. A `Subscription` is not another `Observable`.
It's the end of the line for observables. We can't call `map` on it or call `subscribe` again. It's the end of the line for observables. We can't call `map` on it or call `subscribe` again.
The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`. The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`.
但`subscribe`方法返回一个`Subscription`对象。但`Subscription`不是另一个`Observable`。
它是可观察对象的末端。我们不能在它上面调用`map`函数或再次调用`subscribe`函数。
`Subscription`对象的设计目的是不同的,这从它的主方法`unsubscribe`就能看出来。
Learn more about observables to understand the implications and consequences of subscriptions. Learn more about observables to understand the implications and consequences of subscriptions.
请学习更多关于可观察对象的知识,来理解订阅的实现和效果。
h2#cors Cross-origin requests: Wikipedia example h2#cors Cross-origin requests: Wikipedia example
h2#cors 跨域请求Wikipedia范例
:marked :marked
We just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service. We just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service.
This is the most common approach for server communication. This is the most common approach for server communication.
It doesn't work in all scenarios. It doesn't work in all scenarios.
我们刚刚学习了用!{_Angular_Http}服务发起`XMLHttpRequests`。
这是与服务器通讯时最常用的方法。
但它不适合所有场景。
For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page. For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page.
The *origin* is the combination of URI scheme, hostname and port number. The *origin* is the combination of URI scheme, hostname and port number.
This is called the [Same-origin Policy](https://en.wikipedia.org/wiki/Same-origin_policy). This is called the [Same-origin Policy](https://en.wikipedia.org/wiki/Same-origin_policy).
出于安全的考虑Web浏览器会阻止到与当前页面不“同源”的远端服务器的`XHR`调用。
所谓*源*就是URI的协议(scheme)、主机名(host)和端口号(port)这几部分的组合。
这被称为[同源策略](https://en.wikipedia.org/wiki/Same-origin_policy)。
.l-sub-section .l-sub-section
:marked :marked
Modern browsers do allow `XHR` requests to servers from a different origin if the server supports the Modern browsers do allow `XHR` requests to servers from a different origin if the server supports the
[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) protocol. [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) protocol.
If the server requires user credentials, we'll enable them in the [request headers](#headers). If the server requires user credentials, we'll enable them in the [request headers](#headers).
在现代浏览器中,如果服务器支持[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)协议,那么也可以向不同源的服务器发起`XHR`请求。
如果服务器要请求用户凭证,我们就在[请求头](#headers)中启用它们。
:marked :marked
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)。
Wikipedia就是一个这样的服务器。
.l-sub-section .l-sub-section
:marked :marked
This [StackOverflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP. This [StackOverflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP.
这个[StackOverflow上的答案](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584)覆盖了关于JSONP的很多细节。
:marked :marked
### Search wikipedia ### Search wikipedia
### 搜索Wikipedia
Let's build a simple search that shows suggestions from wikipedia as we type in a text box. Let's build a simple search that shows suggestions from wikipedia as we type in a text box.
我们来构建一个简单的搜索程序当我们在文本框中输入时它会从Wikipedia中获取并显示建议的词汇列表。
figure.image-display figure.image-display
img(src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250") img(src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250")
@ -748,125 +805,215 @@ block wikipedia-jsonp+
The Angular `Jsonp` service both extends the `#{_Http}` service for JSONP and restricts us to `GET` requests. The Angular `Jsonp` service both extends the `#{_Http}` service for JSONP and restricts us to `GET` requests.
All other HTTP methods throw an error because JSONP is a read-only facility. All other HTTP methods throw an error because JSONP is a read-only facility.
Wikipedia提供了一个现代的`CORS` API和一个传统的`JSONP`搜索API。在这个例子中我们使用后者。
Angular的`Jsonp`服务既通过JSONP扩展了`#{_Http}`服务,又限制了我们只能用`GET`请求。
尝试调用所有其它HTTP方法都将抛出一个错误这是因为JSONP是只读的。
As always, we wrap our interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`. As always, we wrap our interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`.
像往常一样我们把和Angular数据访问服务进行交互的代码全都封装在一个专门的服务中。我们称之为`WikipediaService`。
+makeExample('server-communication/ts/app/wiki/wikipedia.service.ts',null,'app/wiki/wikipedia.service.ts') +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts',null,'app/wiki/wikipedia.service.ts')
:marked :marked
The constructor expects Angular to inject its `jsonp` service. The constructor expects Angular to inject its `jsonp` service.
We register that service with `JSONP_PROVIDERS` in the [component below](#wikicomponent) that calls our `WikipediaService`. We register that service with `JSONP_PROVIDERS` in the [component below](#wikicomponent) that calls our `WikipediaService`.
这个构造函数期望Angular给它注入一个`jsonp`服务。
我们在[下面这个组件](#wikicomponent)(它会调用`WikipediaService`服务)中,用`JSONP_PROVIDERS`注册了这个服务。
<a id="query-parameters"></a> <a id="query-parameters"></a>
:marked :marked
### Search parameters ### Search parameters
### 搜索参数
The [Wikipedia 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch) The [Wikipedia 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch)
expects four parameters (key/value pairs) to arrive in the request URL's query string. expects four parameters (key/value pairs) to arrive in the request URL's query string.
The keys are `search`, `action`, `format`, and `callback`. The keys are `search`, `action`, `format`, and `callback`.
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中带四个查询参数键/值对格式)。
这些键(key)分别是`search`、`action`、`format`和`callback`。
`search`的值是一个用户提供的打算在Wikipedia中查找的关键字。
另外三个参数是固定值,分别是"opensearch"、"json"和"JSONP_CALLBACK"。
.l-sub-section .l-sub-section
:marked :marked
The `JSONP` technique requires that we pass a callback function name to the server in the query string: `callback=JSONP_CALLBACK`. The `JSONP` technique requires that we pass a callback function name to the server in the query string: `callback=JSONP_CALLBACK`.
The server uses that name to build a JavaScript wrapper function in its response which Angular ultimately calls to extract the data. The server uses that name to build a JavaScript wrapper function in its response which Angular ultimately calls to extract the data.
All of this happens under the hood. All of this happens under the hood.
`JSONP`技术需要我们通过查询参数传给服务器一个回调函数的名字:`callback=JSONP_CALLBACK`。
服务器使用这个名字在它的响应体中构建一个JavaScript包装函数Angular最终会调用这个包装函数来提取出数据。
这些都是Angular在背后默默完成的你不会感受到它。
:marked :marked
If we're looking for articles with the word "Angular", we could construct the query string by hand and call `jsonp` like this: If we're looking for articles with the word "Angular", we could construct the query string by hand and call `jsonp` like this:
如果我们要找那些含有关键字“Angular”的文档我们可以先手工构造出查询字符串并像这样调用`jsonp`
+makeExample('server-communication/ts/app/wiki/wikipedia.service.1.ts','query-string')(format='.') +makeExample('server-communication/ts/app/wiki/wikipedia.service.1.ts','query-string')(format='.')
:marked :marked
In more parameterized examples we might prefer to build the query string with the Angular `URLSearchParams` helper as shown here: In more parameterized examples we might prefer to build the query string with the Angular `URLSearchParams` helper as shown here:
在更为参数化的例子中我们会首选Angular的`URLSearchParams`辅助类来构建查询字符串,就像这样:
+makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','search-parameters','app/wiki/wikipedia.service.ts (search parameters)')(format=".") +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','search-parameters','app/wiki/wikipedia.service.ts (search parameters)')(format=".")
:marked :marked
This time we call `jsonp` with *two* arguments: the `wikiUrl` and an options object whose `search` property is the `params` object. This time we call `jsonp` with *two* arguments: the `wikiUrl` and an options object whose `search` property is the `params` object.
这次我们使用了*两个*参数来调用`jsonp``wikiUrl`和一个配置对象,配置对象的`search`属性是刚构建的这个`params`对象。
+makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','call-jsonp','app/wiki/wikipedia.service.ts (call jsonp)')(format=".") +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','call-jsonp','app/wiki/wikipedia.service.ts (call jsonp)')(format=".")
:marked :marked
`Jsonp` flattens the `params` object into the same query string we saw earlier before putting the request on the wire. `Jsonp` flattens the `params` object into the same query string we saw earlier before putting the request on the wire.
`Jsonp`把`params`对象平面化为一个查询字符串,而这个查询字符串和以前我们直接放在请求中的那个是一样的。
<a id="wikicomponent"></a> <a id="wikicomponent"></a>
:marked :marked
### The WikiComponent ### The WikiComponent
### WikiComponent组件
Now that we have a service that can query the Wikipedia API, Now that we have a service that can query the Wikipedia API,
we turn to the component that takes user input and displays search results. we turn to the component that takes user input and displays search results.
现在我们有了一个可用于查询Wikpedia API的服务
我们重新回到组件中,接收用户输入,并显示搜索结果。
+makeExample('server-communication/ts/app/wiki/wiki.component.ts', null, 'app/wiki/wiki.component.ts') +makeExample('server-communication/ts/app/wiki/wiki.component.ts', null, 'app/wiki/wiki.component.ts')
:marked :marked
The `providers` array in the component metadata specifies the Angular `JSONP_PROVIDERS` collection that supports the `Jsonp` service. The `providers` array in the component metadata specifies the Angular `JSONP_PROVIDERS` collection that supports the `Jsonp` service.
We register that collection at the component level to make `Jsonp` injectable in the `WikipediaService`. We register that collection at the component level to make `Jsonp` injectable in the `WikipediaService`.
该组件元数据中的`providers`数组指定了Angular的`JSONP_PROVIDERS`集合,以支持`Jsonp`服务。
我们把这个集合注册在组件级,以便`Jsonp`可以被注入进`WikipediaService`中。
The component presents an `<input>` element *search box* to gather search terms from the user. The component 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>`元素作为*搜索框*作为从用户那里手机搜索关键字的入口。
并在每次`keyup`事件被触发时,调用`search(term)`方法。
The `search(term)` method delegates to our `WikipediaService` which returns an observable array of string results (`Observable<string[]`). The `search(term)` method delegates to our `WikipediaService` which returns an observable array of string results (`Observable<string[]`).
Instead of subscribing to the observable inside the component as we did in the `HeroListComponent`, Instead of subscribing to the observable inside the component as we did in the `HeroListComponent`,
we forward the observable result to the template (via `items`) where the [async pipe](pipes.html#async-pipe) we forward the observable result to the template (via `items`) where the [async pipe](pipes.html#async-pipe)
in the `ngFor` handles the subscription. in the `ngFor` handles the subscription.
`search(term)`方法委托我们的`WikipediaService`服务来完成实际操作。该服务返回的是一个字符串数组的可观察对象(`Observable<string[]`)。
该组件的内部订阅了这个可观察对象,就像我们曾在`HeroListComponent`中所做的那样,
我们把这个可观察对象作为结果传给模板(通过`items`属性),模板中`ngFor`上的[async(异步)管道](pipes.html#async-pipe)会对这个订阅进行处理。
.l-sub-section .l-sub-section
:marked :marked
We often use the [async pipe](pipes.html#async-pipe) in read-only components where the component has no need to interact with the data. We often use the [async pipe](pipes.html#async-pipe) in read-only components where the component has no need to interact with the data.
We couldn't use the pipe in the `HeroListComponent` because the "add hero" feature pushes newly created heroes into the list. We couldn't use the pipe in the `HeroListComponent` because the "add hero" feature pushes newly created heroes into the list.
我们通常在只读组件中使用[async管道](pipes.html#async-pipe),这种组件不需要与数据进行互动。
但我们不能在`HeroListComponent`中使用这个管道,这是因为“添加新英雄”特性会把一个新创建的英雄追加到英雄列表中。
:marked :marked
## Our wasteful app ## Our wasteful app
## 我们这个奢侈的应用
Our wikipedia search makes too many calls to the server. Our 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搜索程序触发了过多的服务器调用每次按键发一次
这样效率很低,而且在流量受限的移动设备上会显得过于昂贵。
### 1. Wait for the user to stop typing ### 1. Wait for the user to stop typing
### 1. 等用户停止输入
At the moment we call the server after every key stroke. At the moment we call the server after every key stroke.
The app should only make requests when the user *stops typing* . The app should only make requests when the user *stops typing* .
Here's how it *should* work &mdash; and *will* work &mdash; when we're done refactoring: Here's how it *should* work &mdash; and *will* work &mdash; when we're done refactoring:
现在,我们会在每次按键之后调用服务器。
但合理的方式是只在用户*停止输入*之后才发起请求。
这是它*应该*而且*即将使用*的工作方式,我们马上就重构它:
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
### 2. Search when the search term changes ### 2. Search when the search term changes
### 2. 当搜索关键字变化了才搜索
Suppose the user enters the word *angular* in the search box and pauses for a while. Suppose the user enters the word *angular* in the search box and pauses for a while.
The application issues a search request for *Angular*. The application issues a search request for *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.
然后,用户用退格键删除了最后三个字符*lar*,并且毫不停顿的重新输入了*lar*。
搜索关键词仍然是“angular”。这时应用程序不应该发起另一个请求。
### 3. Cope with out-of-order responses ### 3. Cope with out-of-order responses
### 3. 对付乱序响应体
The user enters *angular*, pauses, clears the search box, and enters *http*. The user enters *angular*, pauses, clears the search box, and enters *http*.
The application issues two search requests, one for *angular* and one for *http*. The application issues two search requests, one for *angular* and one for *http*.
用户输入了*angular*,暂停,清除搜索框,然后输入*http*。
应用程序发起了两个搜索请求,一个搜*angular*,一个搜*http*。
Which response will arrive first? We can't be sure. Which response will arrive first? We can't be sure.
A load balancer could dispatch the requests to two different servers with different response times. A load balancer could dispatch the requests to two different servers with different response times.
The results from the first *angular* request might arrive after the later *http* results. The results from the first *angular* request might arrive after the later *http* results.
The user will be confused if we display the *angular* results to the *http* query. The user will be confused if we display the *angular* results to the *http* query.
哪一个响应会先回来?我们是没法保证的。
负载均衡器可能把这个请求分发给了响应时间不同的两台服务器。
来自第一个*angular*请求的结果可能晚于稍后的*http*的结果。
用户可能会困惑:为什么在*http*请求中显示了*angular*的结果。
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*的结果后返回,也不会发生这样的混乱。
<a id="more-observables"></a> <a id="more-observables"></a>
## More fun with Observables ## More fun with Observables
## Observable的更多乐趣
We can address these problems and improve our app with the help of some nifty observable operators. We can address these problems and improve our app with the help of some nifty observable operators.
借助一些漂亮的可观察对象操作函数,我们可以解决这些问题,并提升我们的应用程序。
We could make our changes to the `WikipediaService`. We could make our changes to the `WikipediaService`.
But we sense that our concerns are driven by the user experience so we update the component class instead. But we sense that our concerns are driven by the user experience so we update the component class instead.
我们本可以把这些改动合并进`WikipediaService`中,但别忘了我们的关注点应该是由用户体验驱动的,所以,还是把它放到组件类中吧。
+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
We made no changes to the template or metadata, confining them all to the component class. We made no changes to the template or metadata, confining them all to the component class.
Let's review those changes. Let's review those changes.
我们没有修改模板或元数据,只改了组件类。
我们来回顾一下这些改动。
### Create a stream of search terms ### Create a stream of search terms
### 创建一个有搜索关键字组成的“流(Stream)”
We're binding to the search box `keyup` event and calling the component's `search` method after each keystroke. We're binding to the search box `keyup` event and calling the component's `search` method after each keystroke.
我们正在绑定到搜索框的`keyup`事件,并且在每次按键之后调用组件的`search`方法。
We turn these events into an observable stream of search terms using a `Subject` We turn these events into an observable stream of search terms using a `Subject`
which we import from the RxJS observable library: which we import from the RxJS observable library:
我们借助`Subject`类把这些事件转变成一个由搜索关键字组成的可观察的“流”对象。`Subject`是我们从RxJS库中导入的
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject') +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject')
:marked :marked
Each search term is a string, so we create a new `Subject` of type `string` called `searchTermStream`. Each search term is a string, so we create a new `Subject` of type `string` called `searchTermStream`.
After every keystroke, the `search` method adds the search box value to that stream After every keystroke, the `search` method adds the search box value to that stream
via the subject's `next` method. via the subject's `next` method.
每个搜索关键字都是一个字符串,所以我们创建了一个新的`string`类型的`Subject`,并把它称为`searchTermStream`。
在每次按键之后,`search`方法都会通过`Subject`的`next`方法把搜索框的值添加到流中。
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject')(format='.') +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject')(format='.')
:marked :marked
### Listen for search terms ### Listen for search terms
### 监听搜索关键字
Earlier, we passed each search term directly to the service and bound the template to the service results. Earlier, we passed each search term directly to the service and bound the template to the service results.
Now we listen to the *stream of terms*, manipulating the stream before it reaches the `WikipediaService`. Now we listen to the *stream of terms*, manipulating the stream before it reaches the `WikipediaService`.
以前,我们每次都把搜索关键字直接传给服务,并且把模板绑定到服务返回的结果。
而现在我们在监听*关键字组成的流*,并在把它传给`WikipediaService`之前操作这个流。
+makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators')(format='.') +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators')(format='.')
:marked :marked
We wait for the user to stop typing for at least 300 milliseconds We wait for the user to stop typing for at least 300 milliseconds
@ -874,35 +1021,59 @@ block wikipedia-jsonp+
Only changed search values make it through to the service Only changed search values make it through to the service
([distinctUntilChanged](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md)). ([distinctUntilChanged](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md)).
我们先等待用户停止输入至少300毫秒
([debounceTime](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md))。
只有当搜索关键字变化的时候,才把它传给服务
([distinctUntilChanged](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md))。
The `WikipediaService` returns a separate observable of string arrays (`Observable<string[]>`) for each request. The `WikipediaService` returns a separate observable of string arrays (`Observable<string[]>`) for each request.
We could have multiple requests *in flight*, all awaiting the server's reply, We could have multiple requests *in flight*, all awaiting the server's reply,
which means multiple *observables-of-strings* could arrive at any moment in any order. which means multiple *observables-of-strings* could arrive at any moment in any order.
`WikipediaService`服务为每个请求返回一个独立的可观察的字符串数组(`Observable<string[]>`)。
我们可以同时有多个*发送中*的请求,它们都在等服务器的回复,
这意味着多个*可观察的字符串数组*有可能在任何时刻以任何顺序抵达。
The [switchMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md) The [switchMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)
(formerly known as `flatMapLatest`) returns a new observable that combines these `WikipediaService` observables, (formerly known as `flatMapLatest`) returns a new observable that combines these `WikipediaService` observables,
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`)
返回一个新的可观察对象,它组合了所有这些“可观察的字符串数组”,重新按照它们的原始请求顺序进行排列,然后把最近的一个搜索结果交付给调用者。
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.
于是,最终显示的搜索结果列表和用户输入的搜索关键字在顺序上保持了一致。
.l-sub-section .l-sub-section
:marked :marked
We added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class We added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class
in `rxjs-operators` as [described above](#rxjs) in `rxjs-operators` as [described above](#rxjs)
在[前面提过的](#rxjs)`rxjs-operators`文件中,我们把`debounceTime`、`distinctUntilChanged`和`switchMap`操作函数加到了RxJS的`Observable`类中。
a#in-mem-web-api a#in-mem-web-api
.l-main-section .l-main-section
:marked :marked
## Appendix: Tour of Heroes in-memory server ## Appendix: Tour of Heroes in-memory server
## 附录:《英雄指南》的内存(in-memory)服务器
If we only cared to retrieve data, we could tell Angular to get the heroes from a `heroes.json` file like this one: If we only cared to retrieve data, we could tell Angular to get the heroes from a `heroes.json` file like this one:
如果我们只关心获取到的数据我们可以告诉Angular从一个从一个`heroes.json`文件中获取英雄列表,就像这样:
+makeJson('server-communication/ts/app/heroes.json', null, 'app/heroes.json')(format=".") +makeJson('server-communication/ts/app/heroes.json', null, 'app/heroes.json')(format=".")
.l-sub-section .l-sub-section
:marked :marked
We wrap the heroes array in an object with a `data` property for the same reason that a data server does: We wrap the heroes array in an object with a `data` property for the same reason that a data server does:
to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk) to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk)
posed by top-level JSON arrays. posed by top-level JSON arrays.
我们把英雄数组包装在一个带有`data`属性的对象中,就像一个数据服务器所做的那样:
用于缓解由顶层的JSON数组带来的[安全风险](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk)。
:marked :marked
We'd set the endpoint to the JSON file like this: We'd set the endpoint to the JSON file like this:
我们要像这样把端点设置为这个JSON文件
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".")
- var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : '' - var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : ''
@ -912,6 +1083,10 @@ a#in-mem-web-api
We didn't want the hassle of setting up and maintaining a real server for this chapter. We didn't want the hassle of setting up and maintaining a real server for this chapter.
So we turned to an *in-memory web API simulator* instead. So we turned to an *in-memory web API simulator* instead.
这在*“获取”英雄数据*的场景下确实能工作,但我们还想*保存*数据。我们不能把这些改动保存到JSON文件中我们需要一个Web API服务器。
在本章中,我们不想惹上配置和维护真实服务器的那些麻烦事。
所以,我们转而使用一种*内存Web API仿真器*代替它。
.l-sub-section .l-sub-section
:marked :marked
The in-memory web api is not part of the Angular core. The in-memory web api is not part of the Angular core.
@ -919,28 +1094,48 @@ a#in-mem-web-api
that we installed with npm (see `package.json`) and that we installed with npm (see `package.json`) and
registered for module loading by SystemJS (see `systemjs.config.js`) registered for module loading by SystemJS (see `systemjs.config.js`)
内存Web API不是Angular内核的一部分。
它是一个可选的服务,来自独立的`angular2-in-memory-web-api`库。我们可以通过npm(参见`package.json`)来安装它,
并且通过SystemJS(参见`systemjs.config.js`)把它注册进模块加载器。
:marked :marked
The in-memory web API gets its data from #{_a_ca_class_with} a `createDb()` The in-memory web API gets its data from #{_a_ca_class_with} a `createDb()`
method that returns a map whose keys are collection names and whose values method that returns a map whose keys are collection names and whose values
are #{_array}s of objects in those collections. are #{_array}s of objects in those collections.
内存Web API从一个带有`createDb()`方法的自定义类中获取数据并且返回一个map它的主键(key)是一组名字,而值(value)是一组与之对应的对象数组。
Here's the class we created for this sample based on the JSON data: Here's the class we created for this sample based on the JSON data:
这里是与范例中那个基于JSON的数据源完成相同功能的类
+makeExample('server-communication/ts/app/hero-data.ts', null, 'app/hero-data.ts')(format=".") +makeExample('server-communication/ts/app/hero-data.ts', null, 'app/hero-data.ts')(format=".")
:marked :marked
Ensure that the `HeroService` endpoint refers to the web API: Ensure that the `HeroService` endpoint refers to the web API:
确保`HeroService`的端点指向了这个Web API
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint')(format=".") +makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint')(format=".")
:marked :marked
Finally, we need to redirect client HTTP requests to the in-memory web API. Finally, we need to redirect client HTTP requests to the in-memory web API.
最后我们需要把来自HTTP客户端的请求重定向到这个内存Web API。
block redirect-to-web-api block redirect-to-web-api
:marked :marked
This redirection is easy to configure because Angular's `http` service delegates the client/server communication tasks This redirection is easy to configure because Angular's `http` service delegates the client/server communication tasks
to a helper service called the `XHRBackend`. to a helper service called the `XHRBackend`.
这次重定向非常易于配置这是因为Angular的`http`服务把客户端/服务器通讯的工作委托给了一个叫做`XHRBackend`的辅助服务。
To enable our server simulation, we replace the default `XHRBackend` service with To enable our server simulation, we replace the default `XHRBackend` service with
the in-memory web API service using standard Angular provider registration techniques. the in-memory web API service using standard Angular provider registration techniques.
We initialize the in-memory web API with *seed data* from the mock hero dataset at the same time. We initialize the in-memory web API with *seed data* from the mock hero dataset at the same time.
要想启用我们的服务模拟器我们通过Angular标准的“供应商注册”技术把默认的`XHRBackend`服务替换为了这个内存Web API服务。
同时,我们使用来自模拟的英雄数据集的*种子数据*初始化了这个内存Web API。
p Here is the revised (and final) version of the #[code #[+adjExPath('app/main.ts')]] demonstrating these steps. p Here is the revised (and final) version of the #[code #[+adjExPath('app/main.ts')]] demonstrating these steps.
p 下面是修改过的(也是最终的)#[code #[+adjExPath('app/main.ts')]]版本,用于演示这些步骤。
+makeExample('server-communication/ts/app/main.ts', 'final', 'app/main.ts (final)')(format=".") +makeExample('server-communication/ts/app/main.ts', 'final', 'app/main.ts (final)')(format=".")
p See the full source code in the #[+liveExampleLink2()]. p See the full source code in the #[+liveExampleLink2()].
p 要想查看完整的源代码,参见#[+liveExampleLink2('浏览器中运行在线版')]。