HttpClient¶
Angular作为前台要和后台通讯就要用到HttpClient
现代浏览器支持使用两种不同的 API 发起 HTTP 请求:XMLHttpRequest 接口和 fetch() API。
@angular/common/http 中的 HttpClient 类为 Angular 应用程序提供了一个简化的 API 来实现 HTTP 客户端功能。它基于浏览器提供的 XMLHttpRequest 接口。 HttpClient 带来的其它优点包括:可测试性、强类型的请求和响应对象、发起请求与接收响应时的拦截器支持,以及更好的、基于可观察(Observable)对象的 API 以及流式错误处理机制。
准备工作¶
要想使用 HttpClient,就要先导入 Angular 的 HttpClientModule。大多数应用都会在根模块 AppModule 中导入它。
app.module.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; //导入模块 import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule, //导入模块 ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
新建一个HttpService,来处理所有http的请求:
1 | ng generate service Http |
然后在新建的http.service.ts类中进行依赖注入:
1 2 3 4 5 6 7 8 9 10 | import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class HttpService { constructor(private http: HttpClient) { } } |
获取JSON数据¶
应用通常会从服务器上获取 JSON 数据。 比如,该应用可能要从服务器上获取配置文件 config.json,其中指定了一些特定资源的 URL。
在assets文件夹下新建一个json文件:
assets/config.json:
1 2 3 4 | { "heroesUrl": "api/heroes", "textfile": "assets/textfile.txt" } |
然后HttpService通过HttpClient的get方法获取这个文件:
http.service.ts:
1 2 3 4 5 | configUrl = 'assets/config.json'; getConfig() { return this.http.get(this.configUrl); } |
然后用对话框将其显示出来:
app.component.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import { Component, OnInit } from '@angular/core'; import { HttpService } from './http.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit{ constructor(private http: HttpService) { } ngOnInit(){ this.http.getConfig() .subscribe((data: Config) => alert("heroesUrl:"+data['heroesUrl']+" textfile:"+data['textfile']) //取得到数据后直接用对话框将内容显示出来 ); } } class Config{ //定义一个Config类来存放数据 heroesUrl: string; textfile: string; } |
这里使用了一个subscribe方法,这个方法会在http完成异步数据请求后调用,可以在这个方法中来处理接收到数据后的操作
lambada表达式中,(data: Config)定义了数据的类型,然后通过data['heroesUrl']的方式来取得数据,除此之外,也可以直接定义get方法返回的数据类型
将http.service.ts改成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Config } from 'protractor'; @Injectable({ providedIn: 'root' }) export class HttpService { configUrl = 'assets/config.json'; constructor(private http: HttpClient) { } getConfig() { return this.http.get<Config>(this.configUrl); } } |
这样angular就知道这个get方法请求的数据类型是Config,在app.component.ts可以直接写成:
1 2 3 4 5 6 | ngOnInit(){ this.http.getConfig() .subscribe((data: Config) => alert("heroesUrl:"+data.heroesUrl+" textfile:"+data.textfile) ); } |
读取完整的响应体¶
响应体可能并不包含你需要的全部信息。有时候服务器会返回一个特殊的响应头或状态码,以标记出特定的条件,因此读取它们可能是必要的。
要这样做,你就要通过 observe 选项来告诉 HttpClient,你想要完整的响应信息,而不是只有响应体:
http.service.ts:
1 2 3 4 | getConfigResponse(): Observable<HttpResponse<Config>> { return this.http.get<Config>( this.configUrl, { observe: 'response' }); } |
现在 HttpClient.get() 会返回一个 HttpResponse 类型的 Observable,而不只是 JSON 数据。
该组件的 showConfigResponse() 方法会像显示配置数据一样显示响应头:
app.component.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 | showConfigResponse() { this.http.getConfigResponse() // resp is of type `HttpResponse<Config>` .subscribe(resp => { // display its headers const keys = resp.headers.keys(); this.headers = keys.map(key => `${key}: ${resp.headers.get(key)}`); // access the body directly, which is typed as `Config`. this.config = { ... resp.body }; }); } |
该响应对象具有一个带有正确类型的 body 属性。
错误处理¶
如果这个请求导致了服务器错误怎么办?甚至,在烂网络下请求都没到服务器该怎么办?HttpClient 就会返回一个错误(error)而不再是成功的响应。
通过在 .subscribe() 中添加第二个回调函数,你可以在组件中处理它:
app.component.ts:
1 2 3 4 5 6 7 | showConfig() { this.http.getConfig() .subscribe( (data: Config) => this.config = { ...data }, // success path error => this.error = error // error path ); } |
检测错误的发生是第一步,不过如果知道具体发生了什么错误才会更有用。上面例子中传给回调函数的 err 参数的类型是 HttpErrorResponse,它包含了这个错误中一些很有用的信息。
可能发生的错误分为两种。如果后端返回了一个失败的返回码(如 404、500 等),它会返回一个错误响应体。
或者,如果在客户端这边出了错误(比如在 RxJS 操作符 (operator) 中抛出的异常或某些阻碍完成这个请求的网络错误),就会抛出一个 Error 类型的异常。
HttpClient 会在 HttpErrorResponse 中捕获所有类型的错误信息,你可以查看这个响应体以了解到底发生了什么。
错误的探查、解释和解决是你应该在服务中做的事情,而不是在组件中。
你可能首先要设计一个错误处理器,就像这样:
http.service.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { // A client-side or network error occurred. Handle it accordingly. console.error('An error occurred:', error.error.message); } else { // The backend returned an unsuccessful response code. // The response body may contain clues as to what went wrong, console.error( `Backend returned code ${error.status}, ` + `body was: ${error.error}`); } // return an observable with a user-facing error message return throwError( 'Something bad happened; please try again later.'); } |
注意,该处理器返回一个带有用户友好的错误信息的 RxJS ErrorObservable 对象。 该服务的消费者期望服务的方法返回某种形式的 Observable,就算是“错误的”也可以。
现在,你获取了由 HttpClient 方法返回的 Observable,并把它们通过管道传给错误处理器。
http.service.ts:
1 2 3 4 5 6 | getConfig() { return this.http.get<Config>(this.configUrl) .pipe( catchError(this.handleError) ); } |
有时候,错误只是临时性的,只要重试就可能会自动消失。 比如,在移动端场景中可能会遇到网络中断的情况,只要重试一下就能拿到正确的结果。
RxJS 库提供了几个 retry 操作符,它们值得仔细看看。 其中最简单的是 retry(),它可以对失败的 Observable 自动重新订阅几次。对 HttpClient 方法调用的结果进行重新订阅会导致重新发起 HTTP 请求。
把它插入到 HttpClient 方法结果的管道中,就放在错误处理器的紧前面。
http.service.ts:
1 2 3 4 5 6 7 | getConfig() { return this.http.get<Config>(this.configUrl) .pipe( retry(3), // retry a failed request up to 3 times catchError(this.handleError) // then handle the error ); } |
把数据发送到服务器¶
除了从服务器获取数据之外,HttpClient 还支持修改型的请求,也就是说,通过 PUT、POST、DELETE 这样的 HTTP 方法把数据发送到服务器。
本指南中的这个范例应用包括一个简化版本的《英雄指南》,它会获取英雄数据,并允许用户添加、删除和修改它们。
下面的这些章节中包括该范例的 HeroesService 中的一些方法片段。
添加请求头¶
很多服务器在进行保存型操作时需要额外的请求头。 比如,它们可能需要一个 Content-Type 头来显式定义请求体的 MIME 类型。 也可能服务器会需要一个认证用的令牌(token)。
HeroesService 在 httpOptions 对象中就定义了一些这样的请求头,并把它传给每个 HttpClient 的保存型方法。
http.service.ts:
1 2 3 4 5 6 7 8 | import { HttpHeaders } from '@angular/common/http'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': 'my-auth-token' }) }; |
发起一个 POST 请求¶
应用经常把数据 POST 到服务器。它们会在提交表单时进行 POST。 下面这个例子中,HeroesService 在把英雄添加到数据库中时,就会使用 POST。
http.service.ts:
1 2 3 4 5 6 7 | /** POST: add a new hero to the database */ addHero (hero: Hero): Observable<Hero> { return this.http.post<Hero>(this.heroesUrl, hero, httpOptions) .pipe( catchError(this.handleError('addHero', hero)) ); } |
addHero方法中,post方法的第一个参数(this.heroesUrl)代表要请求的链接
第二个参数(hero)代表将传送给服务器的数据
第三个参数(httpOptions)代表了发送给服务器的http请求头
发起 DELETE 请求¶
该应用可以把英雄的 id 传给 HttpClient.delete 方法的请求 URL 来删除一个英雄。
http.service.ts:
1 2 3 4 5 6 7 8 | /** DELETE: delete the hero from the server */ deleteHero (id: number): Observable<{}> { const url = `${this.heroesUrl}/${id}`; // DELETE api/heroes/42 return this.http.delete(url, httpOptions) .pipe( catchError(this.handleError('deleteHero')) ); } |
发起 PUT 请求¶
应用可以发送 PUT 请求,来使用修改后的数据完全替换掉一个资源。 下面的 Service 例子和 POST 的例子很像。
http.service.ts:
1 2 3 4 5 6 7 | /** PUT: update the hero on the server. Returns the updated hero upon success. */ updateHero (hero: Hero): Observable<Hero> { return this.http.put<Hero>(this.heroesUrl, hero, httpOptions) .pipe( catchError(this.handleError('updateHero', hero)) ); } |