HTTP 客户端

HTTP is the primary protocol for browser/server communication.

HTTP 是浏览器和服务器之间通讯的主要协议。

The WebSocket protocol is another important communication technology; it isn't covered in this page.

WebSocket协议是另一种重要的通讯技术,但本章不会涉及它。

Modern browsers support two HTTP-based APIs: XMLHttpRequest (XHR) and JSONP. A few browsers also support Fetch.

现代浏览器支持两种基于 HTTP 的 API: XMLHttpRequest (XHR)JSONP。少数浏览器还支持 Fetch

The Angular HTTP library simplifies application programming with the XHR and JSONP APIs. This page covers:

Angular HTTP 库简化了 XHRJSONP API 的编程,这就是本章所要讲的。

A live example illustrates these topics.

我们在在线例子中展示了这些主题。

Demos

演示

This page describes server communication with the help of the following demos:

本章通过下面这些演示,描述了服务端通讯的用法。

The root AppComponent orchestrates these demos:

这些演示由根组件AppComponent统一演示。

app/app.component.ts

import { Component } from '@angular/core'; // Add the RxJS Observable operators. import './rxjs-operators'; @Component({ selector: 'my-app', template: ` <hero-list></hero-list> <hero-list-promise></hero-list-promise> <my-wiki></my-wiki> <my-wiki-smart></my-wiki-smart> ` }) export class AppComponent { }

There is nothing remarkable here except for the import of RxJS operators, which is described later.

这里唯一值得注意的是对 RxJS 操作符的导入,后面有详细介绍。

// Add the RxJS Observable operators. import './rxjs-operators';

Providing HTTP services

提供 HTTP 服务

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 库所提供的一系列服务之一。

When importing from the @angular/http module, SystemJS knows how to load services from the Angular HTTP library because the systemjs.config.js file maps to that module name.

当我们从@angular/http模块中导入服务时,SystemJS 知道该如何从 Angular HTTP 库中加载它们, 这是因为systemjs.config.js文件已经注册过这个模块名。

Before you can use the Http client, you need to register it as a service provider with the dependency injection system.

要想使用Http客户端,你需要先通过依赖注入系统把它注册成一个服务提供商。

Read about providers in the Dependency Injection page.

关于提供商的更多信息,见依赖注入

Register providers by importing other NgModules to the root NgModule in app.module.ts.

app.module.ts中通过导入其他模块来注册提供商到根 NgModule。

app/app.module.ts (v1)

import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule, JsonpModule } from '@angular/http'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule, JsonpModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }

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 on the import statement.

我们从导入所需的成员开始,它们中的大多数我们都熟悉了,只有HttpModuleJsonpModule是新面孔。 关于导入和相关术语的更多信息,见 MDN reference 中的import语句。

To add these modules to the application, pass them to the imports array in the root @NgModule.

只要把它们传给根模块的imports数组,就可以把这些模块加入应用。

The HttpModule is necessary for making HTTP calls. Though the JsonpModule isn't necessary for plain HTTP, there is a JSONP demo later in this page. Loading its module now saves time.

我们需要 HttpModule 来发起 HTTP 调用。 普通的 HTTP 调用并不需要用到 JsonpModule, 不过稍后我们就会演示对 JSONP 的支持, 所以现在就加载它,免得再回来改浪费时间。

The Tour of Heroes HTTP client demo

《英雄指南》的 HTTP 客户端演示

The first demo is a mini-version of the tutorial's "Tour of Heroes" (ToH) application. 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)》教程的一个迷你版。 这个版本从服务器获取一些英雄,把它们显示在列表中,还允许我们添加新的英雄并将其保存到服务器。 借助 Angular Http客户端,我们通过XMLHttpRequest (XHR)与服务器通讯。

It works like this:

它跑起来是这样的:

ToH mini app

This demo has a single component, the HeroListComponent. Here's its template:

这个演示是一个单一组件HeroListComponent,其模板如下:

app/toh/hero-list.component.html (template)

<h1>Tour of Heroes ({{mode}})</h1> <h3>Heroes:</h3> <ul> <li *ngFor="let hero of heroes">{{hero.name}}</li> </ul> <label>New hero name: <input #newHeroName /></label> <button (click)="addHero(newHeroName.value); newHeroName.value=''">Add Hero</button> <p class="error" *ngIf="errorMessage">{{errorMessage}}</p>

It presents the list of heroes with an ngFor. Below the list is an input box and an Add Hero button where you can enter the names of new heroes and add them to the database. A template reference variable, newHeroName, accesses the value of the input box in the (click) event binding. When the user clicks the button, that value passes to the component's addHero method and then the event binding clears it to make it ready for a new hero name.

它使用ngFor来展现这个英雄列表。 列表的下方是一个输入框和一个 Add Hero 按钮,在那里,我们可以输入新英雄的名字, 并把它们加到数据库中。 在(click)事件绑定中,使用模板引用变量newHeroName来访问这个输入框的值。 当用户点击此按钮时,这个值传给组件的addHero方法,然后清除它,以备输入新英雄的名字。

Below the button is an area for an error message.

按钮的下方是一个错误信息区。

The HeroListComponent class

HeroListComponent

Here's the component class:

下面是这个组件类:

app/toh/hero-list.component.ts (class)

export class HeroListComponent implements OnInit { errorMessage: string; heroes: Hero[]; mode = 'Observable'; constructor (private heroService: HeroService) {} ngOnInit() { this.getHeroes(); } getHeroes() { this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); } addHero (name: string) { if (!name) { return; } this.heroService.addHero(name) .subscribe( hero => this.heroes.push(hero), error => this.errorMessage = <any>error); } }

Angular injects a HeroService into the constructor and the component calls that service to fetch and save data.

Angular会把一个HeroService注入到组件的构造函数中,该组件将调用此服务来获取和保存数据。

The component does not talk directly to the Angular Http client. The component doesn't know or care how it gets the data. It delegates to the HeroService.

这个组件不会直接和 Angular Http客户端打交道! 它既不知道也不关心我们如何获取数据,这些都被委托给了HeroService去做。

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 and rely on Angular to call ngOnInit when it instantiates this component.

虽然在运行期间,组件会在创建之后立刻请求这些英雄数据, 但我们在组件的构造函数中调用此服务的get方法。 而是在ngOnInit生命周期钩子中调用它, Angular 会在初始化该组件时调用ngOnInit方法。

This is a best practice. Components are easier to test and debug when their constructors are simple, and all real work (especially calling a remote server) is handled in a separate method.

这是最佳实践。 当组件的构造函数足够简单,并且所有真实的工作(尤其是调用远端服务器) 都在一个独立的方法中处理时,组件会更加容易测试和调试。

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从服务器上获取的。

Think of an Observable as a stream of events published by some source. To listen for events in this stream, subscribe to the Observable. These subscriptions specify the actions to take when the web request produces a success event (with the hero data in the event payload) or a fail event (with the error in the payload).

我们可以把可观察对象Observable看做一个由某些“源”发布的事件流。 通过订阅到可观察对象Observable,我们监听这个流中的事件。 在这些订阅中,我们指定了当 Web 请求生成了一个成功事件(有效载荷是英雄数据) 或失败事件(有效载荷是错误对象)时该如何采取行动。

With a basic understanding of the component, you're ready to look inside the HeroService.

有了对组件的基本理解,我们可以到HeroService的内部实现中看看。

Fetch data with http.get

通过 http.get 获取数据

In many of the previous samples the app faked the interaction with the server by returning mock heroes in a service like this one:

在前面的很多例子中,我们通过在服务中返回一个模拟的英雄列表来伪造了与服务器的交互过程。就像这样:

import { Injectable } from '@angular/core'; import { Hero } from './hero'; import { HEROES } from './mock-heroes'; @Injectable() export class HeroService { getHeroes(): Promise<Hero[]> { return Promise.resolve(HEROES); } }

You can revise that HeroService to get the heroes from the server using the Angular Http client service:

在本章中,我们会修改HeroService,改用 Angular Http客户端服务来从服务器上获取英雄列表:

app/toh/hero.service.ts (revised)

import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Hero } from './hero'; import { Observable } from 'rxjs/Observable'; @Injectable() export class HeroService { private heroesUrl = 'app/heroes'; // URL to web API constructor (private http: Http) {} getHeroes (): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { let body = res.json(); return body.data || { }; } private handleError (error: Response | any) { // In a real world app, we might use a remote logging infrastructure let errMsg: string; if (error instanceof Response) { const body = error.json() || ''; const err = body.error || JSON.stringify(body); errMsg = `${error.status} - ${error.statusText || ''} ${err}`; } else { errMsg = error.message ? error.message : error.toString(); } console.error(errMsg); return Observable.throw(errMsg); } }

Notice that the Angular Http client service is injected into the HeroService constructor.

注意,这个 Angular Http客户端服务被注入到了HeroService的构造函数中。

constructor (private http: Http) {}

Look closely at how to call http.get:

仔细看看我们是如何调用http.get

app/toh/hero.service.ts (getHeroes)

getHeroes (): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); }

You pass the resource URL to get and it calls the server which returns heroes.

我们把资源的 URL 传进get函数,它调用了服务器,而服务器应该返回英雄列表。

The server returns heroes once you've set up the in-memory web api described in the appendix below. Alternatively, you can temporarily target a JSON file by changing the endpoint URL:

一旦我们按附录中所描述的那样准备好了内存 Web API,它将返回英雄列表。 但目前,你可以临时性地使用一个 JSON 文件,修改一下 URL:

private heroesUrl = 'app/heroes.json'; // URL to JSON file

If you are familiar with asynchronous methods in modern JavaScript, you might expect the get method to return a promise. You'd expect to chain a call to then() and extract the heroes. Instead you're calling a map() method. Clearly this is not a promise.

返回值可能会让我们感到意外。 对熟悉现代 JavaScript 中的异步调用方法的人来说,我们期待get方法返回一个承诺 (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.

事实上,http.get方法返回了一个 HTTP Response 类型的可观察对象 (Observable<Response>),这个对象来自 RxJS 库,而map是 RxJS 的操作符之一。

RxJS library

RxJS库

RxJS ("Reactive Extensions") is a 3rd party library, endorsed by Angular, that implements the asynchronous observable pattern.

RxJS ("Reactive Extensions" 的缩写) 是一个被 Angular 认可的第三方库, 它实现了异步可观察对象 (asynchronous observable) 模式。

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 应用中使用非常广泛。

The app needs it when working with the HTTP client. Additionally, you must take a critical extra step to make RxJS observables usable.

HTTP 客户端更需要它。经过一个关键步骤,我们才能让 RxJS 可观察对象可用。

Enable RxJS operators

启用 RxJS 操作符

The RxJS library is large. Size matters when building a production application and deploying it to mobile devices. You should include only necessary features.

RxJS 库实在是太大了。 当构建一个产品级应用,并且把它发布到移动设备上的时候,大小就会成为一个问题。 我们应该只包含那些我们确实需要的特性。

Accordingly, Angular exposes a stripped down version of Observable in the rxjs/Observable module that lacks most of the operators such as the map method you called above in getHeroes.

因此,Angular 在rxjs/Observable模块中导出了一个精简版的Observable类,这个版本缺少很多操作符, 比如我们在上面的getHeroes方法中用过的map函数。

It's up to you to add the operators you need.

这让我们可以自由决定添加哪些操作符。

You could add every RxJS operator with a single import statement. While that is the easiest thing to do, you'd pay a penalty in extended launch time and application size because the full library is so big.

我们可以通过一条 import 语句把每个 RxJS 操作符都添加进来。 虽然这是最简单的方式,但我们也得付出代价,主要是在启动时间和应用大小上,因为完整的库实在太大了。 而我们其实只需要用到少量操作符。

Since this app only uses a few operators, it's better to import each Observable operator and static class method, one-by-one, for a custom Observable implementation tuned precisely to the app's requirements. Put the import statements in one app/rxjs-operators.ts file.

因为本应用只使用了少许操作符,所以将一个一个的导入Observable的操作符和静态类方法比较合适, 直到我们得到了一个精确符合我们需求的自定义 Observable 实现。 我们将把这些import语句放进一个app/rxjs-operators.ts文件里。

app/rxjs-operators.ts

// import 'rxjs/Rx'; // adds ALL RxJS statics & operators to Observable // See node_module/rxjs/Rxjs.js // Import just the rxjs statics and operators needed for THIS app. // Statics import 'rxjs/add/observable/throw'; // Operators import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/toPromise';

If you forget an operator, the TypeScript compiler warns that it's missing and you'll update this file.

如果忘了导入某个操作符,TypeScript 编译器就会警告说找不到它,那时候我们再来更新此文件。

The app doesn't need all of these particular operators in the HeroService — just map, catch and throw. The other operators are for later, in the Wiki example below.

HeroService中,我们并不需要在这里导入的全部操作符 — 我们只用到了mapcatchthrow。 我们稍后的 Wiki 例子中,还会用到其它操作符。

Finally, import rxjs-operator into app.component.ts:

最后,我们把rxjs-operator本身导入app.component.ts文件中:

app/app.component.ts (import rxjs)

// Add the RxJS Observable operators. import './rxjs-operators';

Now continue to the next section to return to the HeroService.

现在,继续到下一节,返回到HeroService

Process the response object

处理响应对象

Remember that the getHeroes() method used an extractData helper method to map the http.get response object to heroes:

记住,getHeroes()借助一个extractData辅助方法来把http.get的响应对象映射成了英雄列表:

app/toh/hero.service.ts (excerpt)

private extractData(res: Response) { let body = res.json(); return body.data || { }; }

The response object doesn't hold the data in a form the app can use directly. You must parse the response data into a JSON object.

这个response对象并没有以一种我们能直接使用的格式来保存数据。 要让它在应用程序中可用,我们就必须把这个响应数据解析成一个 JSON 对象。

Parse to JSON

解析成 JSON

The response data are in JSON string form. The app must parse that string into JavaScript objects by calling response.json().

响应数据是 JSON 字符串格式的。 我们必须把这个字符串解析成 JavaScript 对象 —— 只要调一下response.json()就可以了。

This is not Angular's own design. The Angular HTTP client follows the Fetch specification for the response object returned by the Fetch function. That spec defines a json() method that parses the response body into a JavaScript object.

这不是 Angular 自己的设计。 Angular HTTP 客户端遵循 ES2015 规范来处理Fetch函数返回的响应对象。 此规范中定义了一个json()函数,来把响应体解析成 JavaScript 对象。

Don't expect the decoded JSON to be the heroes array directly. This server always wraps JSON results in an object with a data property. You have to unwrap it to get the heroes. This is conventional web API behavior, driven by security concerns.

我们不应该期待解码后的 JSON 直接就是一个英雄数组。 调用的这个服务器总会把 JSON 结果包装进一个带data属性的对象中。 我们必须解开它才能得到英雄数组。这是一个约定俗成的 Web API 行为规范,它是出于 安全方面的考虑

Make no assumptions about the server API. Not all servers return an object with a data property.

不要对服务端 API 做任何假设。 并非所有服务器都会返回一个带data属性的对象。

Do not return the response object

不要返回响应对象

The getHeroes() method could have returned the HTTP response but this wouldn't be a best practice. The point of a data service is to hide the server interaction details from consumers. The component that calls the HeroService only wants heroes and is kept separate from getting them, the code dealing with where they come from, and the response object.

getHeroes()确实可以返回 HTTP 响应对象,但这不是最佳实践。 数据服务的重点在于,对消费者隐藏与服务器交互的细节。 调用HeroService的组件希望得到英雄数组。 它并不关心我们如何得到它们。 它也不在乎这些数据从哪里来。 毫无疑问,它也不希望直接和一个响应对象打交道。

HTTP GET is delayed
HTTP 的 GET 方法被推迟执行了

The http.get does not send the request just yet. This observable is cold, which means that the request won't go out until something subscribes to the observable. That something is the HeroListComponent.

http.get仍然没有发送请求!这是因为可观察对象是 冷的, 也就是说,只有当某人订阅了这个可观察对象时,这个请求才会被发出。 这个场景中的某人就是HeroListComponent

Always handle errors

总是处理错误

An important part of dealing with I/O is anticipating errors by preparing to catch them and do something with them. One way to handle errors is to pass an error message back to the component for presentation to the user, but only if it says something that the user can understand and act upon.

一旦开始与 I/O 打交道,我们就必须准备好接受墨菲定律:如果一件倒霉事可能发生,它就迟早会发生。 我们可以在HeroService中捕获错误,并对它们做些处理。 只有在用户可以理解并采取相应行动的时候,我们才把错误信息传回到组件,让组件展示给最终用户。

This simple app conveys that idea, albeit imperfectly, in the way it handles a getHeroes error.

在这个简单的应用中,我们在服务和组件中都只提供了最原始的错误处理方式。

app/toh/hero.service.ts (excerpt)

getHeroes (): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); } private handleError (error: Response | any) { // In a real world app, we might use a remote logging infrastructure let errMsg: string; if (error instanceof Response) { const body = error.json() || ''; const err = body.error || JSON.stringify(body); errMsg = `${error.status} - ${error.statusText || ''} ${err}`; } else { errMsg = error.message ? error.message : error.toString(); } console.error(errMsg); return Observable.throw(errMsg); }

The catch operator passes the error object from http to the handleError method. The handleError method transforms the error into a developer-friendly message, logs it to the console, and returns the message in a new, failed observable via Observable.throw.

catch操作符将错误对象传递给httphandleError方法。 服务处理器 (handleError) 把响应对象记录到控制台中, 把错误转换成对用户友好的消息,并且通过Observable.throw来把这个消息放进一个新的、用于表示“失败”的可观察对象。

app/toh/hero.service.ts (excerpt)

getHeroes (): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); } private handleError (error: Response | any) { // In a real world app, we might use a remote logging infrastructure let errMsg: string; if (error instanceof Response) { const body = error.json() || ''; const err = body.error || JSON.stringify(body); errMsg = `${error.status} - ${error.statusText || ''} ${err}`; } else { errMsg = error.message ? error.message : error.toString(); } console.error(errMsg); return Observable.throw(errMsg); }

HeroListComponent error handling

HeroListComponent 错误处理

Back in the HeroListComponent, in heroService.getHeroes(), the subscribe function has a second function parameter to handle the error message. It sets an errorMessage variable that's bound conditionally in the HeroListComponent template.

回到HeroListComponent,这里我们调用了heroService.getHeroes()。我们提供了subscribe函数的第二个参数来处理错误信息。 它设置了一个errorMessage变量,被有条件的绑定到了HeroListComponent模板中。

app/toh/hero-list.component.ts (getHeroes)

getHeroes() { this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); }

Want to see it fail? In the HeroService, reset the api endpoint to a bad value. Afterward, remember to restore it.

想看到它失败时的情况吗?在HeroService中把 API 的端点设置为一个无效值就行了。但别忘了恢复它。

Send data to the server

往服务器发送数据

So far you've seen how to retrieve data from a remote location using an HTTP service. Now you'll add the ability to create new heroes and save them in the backend.

前面我们已经看到如何用一个 HTTP 服务从远端获取数据了。 但我们还能再给力一点,让它可以创建新的英雄,并把它们保存到后端。

You'll write a method for the HeroListComponent to call, an addHero() method, that takes just the name of a new hero and returns an Observable of Hero. It begins like this:

我们将为HeroListComponent创建一个简单的addHero()方法,它将接受新英雄的名字:

addHero (name: string): Observable<Hero> {

To implement it, you must know the server's API for creating heroes.

要实现它,我们得知道关于服务端 API 如何创建英雄的一些细节。

This sample's data server follows typical REST guidelines. It expects a POST request at the same endpoint as GET heroes. It expects the new hero data to arrive in the body of the request, structured like a Hero entity but without the id property. The body of the request should look like this:

我们的数据服务器遵循典型的 REST 指导原则。 它期待在和GET英雄列表的同一个端点上存在一个POST请求。 它期待从请求体 (body) 中获得新英雄的数据,数据的结构和Hero对象相同,但是不带id属性。 请求体应该看起来像这样:

{ "name": "Windstorm" }

The server generates the id and returns the entire JSON representation 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属性中。

Now that you know how the API works, implement addHero()as follows:

现在,知道了这个 API 如何工作,我们就可以像这样实现addHero()了:

app/toh/hero.service.ts (additional imports)

import { Headers, RequestOptions } from '@angular/http';

app/toh/hero.service.ts (addHero)

addHero (name: string): Observable<Hero> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this.heroesUrl, { name }, options) .map(this.extractData) .catch(this.handleError); }

Headers

请求头 (headers)

In the headers object, the Content-Type specifies that the body represents JSON.

我们通过Content-Type头告诉服务器,body 是 JSON 格式的。

Next, the headers object is used to configure the options object. The options object is a new instance of RequestOptions, a class that allows you to specify certain settings when instantiating a request. In this way, headers is one of the RequestOptions.

接下来,使用headers对象来配置options对象。 options对象是RequestOptions的新实例,该类允许你在实例化请求时指定某些设置。这样, HeadersRequestOptions 中的一员。

In the return statement, options is the third argument of the post method, as shown above.

return声明中,options是传给post方法的第三个参数,就像前面见过的那样。

JSON results

JSON 结果

As with getHeroes(), use the extractData() helper to extract the data from the response.

getHeroes()中一样,我们可以使用extractData()辅助函数从响应中提取出数据

Back in the HeroListComponent, 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.

回到HeroListComponent,我们看到该组件的addHero()方法中订阅了这个由服务中addHero()方法返回的可观察对象。 当有数据到来时,它就会把这个新的英雄对象追加 (push) 到heroes数组中,以展现给用户。

app/toh/hero-list.component.ts (addHero)

addHero (name: string) { if (!name) { return; } this.heroService.addHero(name) .subscribe( hero => this.heroes.push(hero), error => this.errorMessage = <any>error); }

Fall back to promises

倒退为承诺 (Promise)

Although the Angular http client API returns an Observable<Response> you can turn it into a Promise<Response>. It's easy to do, and in simple cases, a promise-based version looks much like the observable-based version.

虽然 Angular 的http客户端 API 返回的是Observable<Response>类型的对象,但我们也可以把它转成 Promise<Response>。 这很容易,并且在简单的场景中,一个基于承诺 (Promise) 的版本看起来很像基于可观察对象 (Observable) 的版本。

While promises may be more familiar, observables have many advantages.

可能“承诺”看起来更熟悉一些,但“可观察对象”有很多优越之处。

Here is a comparison of the HeroService using promises versus observables, highlighting just the parts that are different.

下面是使用承诺重写HeroService,要特别注意那些不同的部分。

getHeroes (): Promise<Hero[]> { return this.http.get(this.heroesUrl) .toPromise() .then(this.extractData) .catch(this.handleError); } addHero (name: string): Promise<Hero> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this.heroesUrl, { name }, options) .toPromise() .then(this.extractData) .catch(this.handleError); } private extractData(res: Response) { let body = res.json(); return body.data || { }; } private handleError (error: Response | any) { // In a real world app, we might use a remote logging infrastructure let errMsg: string; if (error instanceof Response) { const body = error.json() || ''; const err = body.error || JSON.stringify(body); errMsg = `${error.status} - ${error.statusText || ''} ${err}`; } else { errMsg = error.message ? error.message : error.toString(); } console.error(errMsg); return Promise.reject(errMsg); } getHeroes (): Observable<Hero[]> { return this.http.get(this.heroesUrl) .map(this.extractData) .catch(this.handleError); } addHero (name: string): Observable<Hero> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this.heroesUrl, { name }, options) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { let body = res.json(); return body.data || { }; } private handleError (error: Response | any) { // In a real world app, we might use a remote logging infrastructure let errMsg: string; if (error instanceof Response) { const body = error.json() || ''; const err = body.error || JSON.stringify(body); errMsg = `${error.status} - ${error.statusText || ''} ${err}`; } else { errMsg = error.message ? error.message : error.toString(); } console.error(errMsg); return Observable.throw(errMsg); }

You can follow the promise then(this.extractData).catch(this.handleError) pattern as in this example.

在本例中,你可以遵循承诺的then(this.extractData).catch(this.handleError)模式。

Alternatively, you can call toPromise(success, fail). The observable's map callback moves to the first success parameter and its catch callback to the second fail parameter in this pattern: .toPromise(this.extractData, this.handleError).

另外,你也可以调用toPromise(success, fail)。可观察对象的map第一个参数为成功时的回调函数, 它的第二个参数用.toPromise(this.extractData, this.handleError)来拦截失败。

The 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.

把诊断信息记录到控制台也只是在承诺的处理链中多了一个then而已。

You have to adjust the calling component to expect a Promise instead of an observable:

我们还得对调用方组件进行调整,让它期待一个Promise而非Observable

getHeroes() { this.heroService.getHeroes() .then( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); } addHero (name: string) { if (!name) { return; } this.heroService.addHero(name) .then( hero => this.heroes.push(hero), error => this.errorMessage = <any>error); } getHeroes() { this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); } addHero (name: string) { if (!name) { return; } this.heroService.addHero(name) .subscribe( hero => this.heroes.push(hero), error => this.errorMessage = <any>error); }

The only obvious difference is that you call then on the returned promise instead of subscribe. Both methods take the same functional arguments.

唯一一个比较明显的不同点是我们调用这个返回的承诺的then方法,而不再是subscribe。 我们给了这两个方法完全相同的调用参数。

The less obvious but critical difference is that these two methods return very different results.

细微却又关键的不同点是,这两个方法返回了非常不同的结果!

The promise-based then returns another promise. You can keep chaining more then and catch calls, getting a new promise each time.

基于承诺的then返回了另一个承诺。我们可以链式调用多个thencatch方法,每次都返回一个新的承诺。

The subscribe method returns a Subscription. A Subscription is not another Observable. It's the end of the line for observables. You can't call map on it or call subscribe again. The Subscription object has a different purpose, signified by its primary method, unsubscribe.

subscribe方法返回一个Subscription对象。但Subscription不是另一个Observable。 它是可观察对象的末端。我们不能在它上面调用map函数或再次调用subscribe函数。 Subscription对象的设计目的是不同的,这从它的主方法unsubscribe就能看出来。

To understand the implications and consequences of subscriptions, watch Ben Lesh's talk on observables or his video course on egghead.io.

要理解订阅的实现和效果,请看 Ben Lesh 关于可观察对象的演讲 或者他在 egghead.io 的课程。

Cross-Origin Requests: Wikipedia example

跨域请求:Wikipedia 范例

You just learned how to make XMLHttpRequests using the Angular Http service. This is the most common approach for server communication, but 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. The origin is the combination of URI scheme, hostname, and port number. This is called the same-origin policy.

出于安全的考虑,网络浏览器会阻止调用与当前页面不“同源”的远端服务器的XHR。 所谓就是 URI 的协议 (scheme)、主机名 (host) 和端口号 (port) 这几部分的组合。 这被称为同源策略

Modern browsers do allow XHR requests to servers from a different origin if the server supports the CORS protocol. If the server requires user credentials, you'll enable them in the request headers.

在现代浏览器中,如果服务器支持 CORS 协议,那么也可以向不同源的服务器发起XHR请求。 如果服务器要请求用户凭证,我们就在请求头中启用它们。

Some servers do not support CORS but do support an older, read-only alternative called JSONP. Wikipedia is one such server.

有些服务器不支持 CORS,但支持一种老的、只读的(译注:即仅支持 GET)备选协议,这就是 JSONP。 Wikipedia就是一个这样的服务器。

This Stack Overflow answer covers many details of JSONP.

这个 StackOverflow 上的答案覆盖了关于 JSONP 的很多细节。

Search wikipedia

搜索 Wikipedia

Here is a simple search that shows suggestions from Wikipedia as the user types in a text box:

我们来构建一个简单的搜索程序,当我们在文本框中输入时,它会从 Wikipedia 中获取并显示建议的词汇列表:

Wikipedia search app (v.1)

Wikipedia offers a modern CORS API and a legacy JSONP search API. This example uses the latter. The Angular Jsonp service both extends the Http service for JSONP and restricts you to GET requests. 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, wrap the interaction with an Angular data access client service inside a dedicated service, here called WikipediaService.

像往常一样,我们把和 Angular 数据访问服务进行交互的代码全都封装在一个专门的服务中。我们称之为WikipediaService

app/wiki/wikipedia.service.ts

import { Injectable } from '@angular/core'; import { Jsonp, URLSearchParams } from '@angular/http'; @Injectable() export class WikipediaService { constructor(private jsonp: Jsonp) {} search (term: string) { let wikiUrl = 'http://en.wikipedia.org/w/api.php'; let params = new URLSearchParams(); params.set('search', term); // the user's search value params.set('action', 'opensearch'); params.set('format', 'json'); params.set('callback', 'JSONP_CALLBACK'); // TODO: Add error handling return this.jsonp .get(wikiUrl, { search: params }) .map(response => <string[]> response.json()[1]); } }

The constructor expects Angular to inject its jsonp service, which is available because JsonpModule is in the root @NgModule imports array in app.module.ts.

这个构造函数期望 Angular 给它注入一个jsonp服务。 前面我们已经把JsonpModule导入到了根模块中,所以这个服务已经可以使用了。

Search parameters

搜索参数

The Wikipedia "opensearch" API expects four parameters (key/value pairs) to arrive in the request URL's query string. 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 other three are the fixed values "opensearch", "json", and "JSONP_CALLBACK" respectively.

Wikipedia 的 'opensearch' API 期待在所请求的 URL 中带四个查询参数(键/值对格式)。 这些键 (key) 分别是searchactionformatcallbacksearch的值是用户提供的用于在 Wikipedia 中查找的关键字。 另外三个参数是固定值,分别是 "opensearch"、"json" 和 "JSONP_CALLBACK"。

The JSONP technique requires that you 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. All of this happens under the hood.

JSONP技术需要我们通过查询参数传给服务器一个回调函数的名字:callback=JSONP_CALLBACK。 服务器使用这个名字在它的响应体中构建一个 JavaScript 包装函数,Angular 最终会调用这个包装函数来提取出数据。 这些都是 Angular 在背后默默完成的,你不会感受到它。

If you're looking for articles with the word "Angular", you could construct the query string by hand and call jsonp like this:

如果我们要找那些含有关键字 “Angular” 的文档,我们可以先手工构造出查询字符串,并像这样调用jsonp

let queryString = `?search=${term}&action=opensearch&format=json&callback=JSONP_CALLBACK`; return this.jsonp .get(wikiUrl + queryString) .map(response => <string[]> response.json()[1]);

In more parameterized examples you could build the query string with the Angular URLSearchParams helper:

在更加参数化的例子中,我们会首选 Angular 的URLSearchParams辅助类来构建查询字符串,就像这样:

app/wiki/wikipedia.service.ts (search parameters)

let params = new URLSearchParams(); params.set('search', term); // the user's search value params.set('action', 'opensearch'); params.set('format', 'json'); params.set('callback', 'JSONP_CALLBACK');

This time you call jsonp with two arguments: the wikiUrl and an options object whose search property is the params object.

这次我们使用了两个参数来调用jsonpwikiUrl和一个配置对象,配置对象的search属性是刚构建的这个params对象。

app/wiki/wikipedia.service.ts (call jsonp)

// TODO: Add error handling return this.jsonp .get(wikiUrl, { search: params }) .map(response => <string[]> response.json()[1]);

Jsonp flattens the params object into the same query string you saw earlier, putting the request on the wire.

Jsonpparams对象平面化为一个查询字符串,而这个查询字符串和以前我们直接放在请求中的那个是一样的。

The WikiComponent

WikiComponent 组件

Now that you have a service that can query the Wikipedia API turn to the component (template and class) that takes user input and displays search results.

现在,我们有了一个可用于查询 Wikpedia API 的服务, 我们重新回到组件中,接收用户输入,并显示搜索结果。

app/wiki/wiki.component.html

<h1>{{title}}</h1> <p><i>{{fetches}}</i></p> <input #term (keyup)="search(term.value)"/> <ul> <li *ngFor="let item of items | async">{{item}}</li> </ul>

app/wiki/wiki.component.ts

import { Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { WikipediaService } from './wikipedia.service'; @Component({ moduleId: module.id, selector: 'my-wiki', templateUrl: 'wiki.component.html', providers: [ WikipediaService ] }) export class WikiComponent { title = 'Wikipedia Demo'; fetches = 'Fetches after each keystroke'; items: Observable<string[]>; search (term: string) { this.items = this.wikipediaService.search(term); } constructor (private wikipediaService: WikipediaService) { } }

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>元素,它是用来从用户获取搜索关键词的搜索框。 在每次keyup事件被触发时,它调用search(term)方法。

wiki/wiki.component.html

<input #term (keyup)="search(term.value)"/>

The component's search(term) method delegates to the WikipediaService, which returns an observable array of string results (Observable<string[]>). Instead of subscribing to the observable inside the component, as in the HeroListComponent, the app forwards the observable result to the template (via items) where the async pipe in the ngFor handles the subscription. Read more about async pipes in the Pipes page.

search(term)方法委托WikipediaService服务来完成实际操作。 该服务返回的是一个字符串数组的可观察对象 (Observable<string[]>)。 没有像HeroListComponent那样在组件内部订阅这个可观察对象, 我们把这个可观察对象作为结果传给模板(通过items属性), 模板中ngFor上的 async(异步)管道会对这个订阅进行处理。 关于异步管理的更多信息,见 Pipes

The async pipe is a good choice in read-only components where the component has no need to interact with the data.

我们通常在只读组件中使用异步管道,这种组件不需要与数据进行互动。

HeroListComponent can't use the pipe because addHero() pushes newly created heroes into the list.

但我们不能在HeroListComponent中使用这个管道,这是因为addHero()会把一个新创建的英雄追加到英雄列表中。

A wasteful app

奢侈的应用程序

The wikipedia search makes too many calls to the server. It is inefficient, and potentially expensive on mobile devices with limited data plans.

这个 Wikipedia 搜索程序触发了过多的服务器调用。 这样效率很低,而且在流量受限的移动设备上会显得过于昂贵。

1. Wait for the user to stop typing

1. 等用户停止输入

Presently, the code calls the server after every key stroke. It should only make requests when the user stops typing . Here's how it will work after refactoring:

我们目前会在每次按键之后调用服务器。 但合理的方式是只在用户停止输入之后才发起请求。 重构之后,它将这样工作:

Wikipedia search app (v.2)

2. Search when the search term changes

2. 当搜索关键字变化了才搜索

Suppose a user enters the word angular in the search box and pauses for a while. 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. The search term is still angular. The app shouldn't make another request.

然后,用户用退格键删除了最后三个字符 lar,并且毫不停顿的重新输入了 lar。 搜索关键词仍然是 “angular”。这时应用程序不应该发起另一个请求。

3. Cope with out-of-order responses

3. 对付乱序响应体

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.

用户输入了 angular,暂停,清除搜索框,然后输入 http。 应用程序发起了两个搜索请求,一个搜 angular,一个搜 http

Which response arrives first? It's unpredictable. 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 user will be confused if the angular results display to the http query.

哪一个响应会先回来?我们是没法保证的。 负载均衡器可能把这个请求分发给了响应时间不同的两台服务器。 搜 angular 的结果可能晚于稍后搜 http 的结果。 用户可能会困惑:为什么搜 http 时显示了关于 angular 的结果。

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 的结果始终在后面返回,就不会发生这样的混乱了。

More fun with observables

Observable 的更多乐趣

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的复本,让它变得更智能。 下面是WikiSmartComponent,它使用同样的模板:

app/wiki/wiki-smart.component.ts

import { Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { WikipediaService } from './wikipedia.service'; @Component({ moduleId: module.id, selector: 'my-wiki-smart', templateUrl: 'wiki.component.html', providers: [ WikipediaService ] }) export class WikiSmartComponent { title = 'Smarter Wikipedia Demo'; fetches = 'Fetches when typing stops'; items: Observable<string[]>; private searchTermStream = new Subject<string>(); search(term: string) { this.searchTermStream.next(term); } constructor (private wikipediaService: WikipediaService) { this.items = this.searchTermStream .debounceTime(300) .distinctUntilChanged() .switchMap((term: string) => this.wikipediaService.search(term)); } }

Create a stream of search terms

创建一个搜索关键字流

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.

模板仍然绑定搜索框的keyup事件,并在每次用户按键时,将搜索框的值传递到组件的search方法。

app/wiki/wiki.component.html (input)

<input #term (keyup)="search(term.value)"/>

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 库导入的SubjectWikiSmartComponent将搜索框的值变为一个搜索关键词流可观察对象:

app/wiki/wiki-smart.component.ts

import { Subject } from 'rxjs/Subject';

The component creates a searchTermStream as a Subject of type string. The search method adds each new search box value to that stream via the subject's next method.

组件创建searchTermStreamstring类型的Subjectsearch方法通过subjectnext方法,将每个新搜索框的值添加到数据流中。

app/wiki/wiki-smart.component.ts

private searchTermStream = new Subject<string>(); search(term: string) { this.searchTermStream.next(term); }

Listen for search terms

监听搜索关键字

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之前操作这个流。

app/wiki/wiki-smart.component.ts

this.items = this.searchTermStream .debounceTime(300) .distinctUntilChanged() .switchMap((term: string) => this.wikipediaService.search(term));

Wait for the user to stop typing for at least 300 milliseconds (debounceTime). Only changed search values make it through to the service (distinctUntilChanged).

我们先等待用户停止输入至少300毫秒 (debounceTime)。 只有当搜索关键字变化的时候,才把它传给服务 (distinctUntilChanged)。

The WikipediaService returns a separate observable of string arrays (Observable<string[]>) for each request. There could be multiple requests in-flight, all awaiting the server's reply, which means multiple observables-of-strings could arrive at any moment in any order.

WikipediaService服务为每个请求返回一个独立的可观察的字符串数组 (Observable<string[]>)。 我们可以同时有多个发送中的请求,它们都在等服务器的回复, 这意味着多个可观察的字符串数组有可能在任何时刻以任何顺序抵达。

The switchMap (formerly known as flatMapLatest) returns a new observable that combines these WikipediaService observables, re-arranges them in their original request order, and delivers to subscribers only the most recent search results.

switchMap(以前叫flatMapLatest) 返回一个新的可观察对象,它组合了所有这些“可观察的字符串数组”,重新按照它们的原始请求顺序进行排列,然后把最近的一个搜索结果交付给订阅者。

The displayed list of search results stays in sync with the user's sequence of search terms.

于是,最终显示的搜索结果列表和用户输入的搜索关键字在顺序上保持了一致。

You added the debounceTime, distinctUntilChanged, and switchMap operators to the RxJS Observable class in rxjs-operators as described above.

前面提过的rxjs-operators文件中,我们把debounceTimedistinctUntilChangedswitchMap操作符加到了 RxJS 的Observable类中。

Guarding against Cross-Site Request Forgery

预防跨站请求伪造攻击

In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting a different web page with malignant code that secretly sends a malicious request to your application's web server,

在一个跨站请求伪造攻击(CSRF 或 XSRF)中,攻击者欺骗用户访问一个不同的网页,它带有恶意代码,秘密向你的应用程序服务器发送恶意请求。

The server and client application must work together to thwart this attack. Angular's Http client does its part by applying a default CookieXSRFStrategy automatically to all requests.

客户端和服务器必须合作来抵挡这种攻击。 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. The HTTP client adds an X-XSRF-TOKEN header with that token value to subsequent requests. 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页头。 服务器接受这个 cookie 和页头,比较它们,只有在它们匹配的时候才处理请求。

See the XSRF topic on the Security page for more information about XSRF and Angular's XSRFStrategy counter measures.

参见安全章的关于 XSRF 讨论,学习更多关于 XSRF 和 Angular 的XSRFStrategy的应对措施。

Appendix: Tour of Heroes in-memory server

附录:《英雄指南》的内存 (in-memory) 服务器

If the app only needed to retrieve data, you could get the heroes from a heroes.json file:

如果我们只关心获取到的数据,我们可以告诉 Angular 从一个heroes.json文件中获取英雄列表,就像这样:

app/heroes.json

{ "data": [ { "id": 1, "name": "Windstorm" }, { "id": 2, "name": "Bombasto" }, { "id": 3, "name": "Magneta" }, { "id": 4, "name": "Tornado" } ] }

You 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 posed by top-level JSON arrays.

我们把英雄数组包装进一个带data属性的对象中,就像一个真正的数据服务器所应该做的那样。 这样可以缓解由顶级 JSON 数组导致的安全风险

You'd set the endpoint to the JSON file like this:

我们要像这样把端点设置为这个 JSON 文件:

app/toh/hero.service.ts

private heroesUrl = 'app/heroes.json'; // URL to JSON file

The get heroes scenario would work, but since the app can't save changes to a JSON file, it needs a web API server. Because there isn't a real server for this demo, it uses an in-memory web API simulator instead.

这在获取英雄数据的场景下确实能工作, 但我们不能把这些改动保存到 JSON 文件中,因此需要一个 Web API 服务器。 因为这个演示程序中并没有一个真实的服务器, 所以,我们使用内存 Web API 仿真器代替它。

The in-memory web api is not part of the Angular core. It's an optional service in its own angular-in-memory-web-api library installed with npm (see package.json) and registered for module loading by SystemJS (see systemjs.config.js).

内存 Web API 不是 Angular 内核的一部分。 它是一个可选的服务,来自独立的angular-in-memory-web-api库。我们可以通过 npm (参见package.json) 来安装它, 并且通过 SystemJS (参见systemjs.config.js) 把它注册进模块加载器。

The in-memory web API gets its data from a custom application class with a createDb() method that returns a map whose keys are collection names and whose values are arrays of objects in those collections.

内存 Web API 从一个带有createDb()方法的自定义类中获取数据,并且返回一个 map,它的主键 (key) 是一组名字,而值 (value) 是一组与之对应的对象数组。

Here's the class for this sample, based on the JSON data:

这里是与范例中基于 JSON 的数据源完成相同功能的类:

app/hero-data.ts

import { InMemoryDbService } from 'angular-in-memory-web-api'; export class HeroData implements InMemoryDbService { createDb() { let heroes = [ { id: 1, name: 'Windstorm' }, { id: 2, name: 'Bombasto' }, { id: 3, name: 'Magneta' }, { id: 4, name: 'Tornado' } ]; return {heroes}; } }

Ensure that the HeroService endpoint refers to the web API:

确保HeroService的端点指向了这个 Web API:

app/toh/hero.service.ts

private heroesUrl = 'app/heroes'; // URL to web API

Finally, redirect client HTTP requests to the in-memory web API by adding the InMemoryWebApiModule to the AppModule.imports list. At the same time, call its forRoot configuration method with the HeroData class.

使用内存 Web API 服务模块很容易配置重定向,将InMemoryWebApiModule添加到AppModule.imports列表中, 同时在HeroData类中调用forRoot配置方法。

app/app.module.ts

InMemoryWebApiModule.forRoot(HeroData)

How it works

工作原理

Angular's http service delegates the client/server communication tasks to a helper service called the XHRBackend.

这次重定向非常容易配置,这是因为 Angular 的http服务把客户端/服务器通讯的工作委托给了一个叫做XHRBackend的辅助服务。

Using standard Angular provider registration techniques, the InMemoryWebApiModule replaces the default XHRBackend service with its own in-memory alternative. 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。

The forRoot method name is a strong reminder that you should only call the InMemoryWebApiModule once, while setting the metadata for the root AppModule. Don't call it again.

forRoot方法的名字告诉我们,应该只在设置根模块AppModule时调用InMemoryWebApiModule一次。不要再次调用它。

Here is the final, revised version of app/app.module.ts, demonstrating these steps.

下面是修改过的(也是最终的)app/app.module.ts版本,用于演示这些步骤。

app/app.module.ts (excerpt)

import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule, JsonpModule } from '@angular/http'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { HeroData } from './hero-data'; import { AppComponent } from './app.component'; import { HeroListComponent } from './toh/hero-list.component'; import { HeroListPromiseComponent } from './toh/hero-list.component.promise'; import { WikiComponent } from './wiki/wiki.component'; import { WikiSmartComponent } from './wiki/wiki-smart.component'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule, JsonpModule, InMemoryWebApiModule.forRoot(HeroData) ], declarations: [ AppComponent, HeroListComponent, HeroListPromiseComponent, WikiComponent, WikiSmartComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }

Import the InMemoryWebApiModule after the HttpModule to ensure that the XHRBackend provider of the InMemoryWebApiModule supersedes all others.

HttpModule之后导入InMemoryWebApiModule,确保XHRBackend的供应商InMemoryWebApiModule取代所有其它的供应商。

See the full source code in the .

要想查看完整的源代码,请参见在线例子