大多数前端应用都需要通过http协议来和后端服务器通信。现代浏览器提供两种不同的API发起http请求:XMLHttpRequest
接口和fetch
API。
基于浏览器的XMLHttpRequest
接口,@angular/common/http
中的HttpClient
类为应用程序提供了一个简化的API来实现http功能。HttpClient
带来的其他优点包括:可测试性、强类型的请求和响应对象、发起请求与接收响应时的拦截器支持,以及更好的、基于可观察(Observeable)对象的错误处理机制。
安装http模块
在使用HttpClient
之前,需要先安装HttpClientModule
以提供它。可以在应用模块中引用,并只需要引入一次。
1 2 3 4 5 6 7 8 9
| import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http';
@NgModule({ imports: [ HttpClientModule, ], }) export class HeroRoutingModule { }
|
上面我们把HttpClientModule
引入到应用模块,并用imports
注入,这样就可以把HttpClient
注入到组件和服务中去了。
发起一个get请求
确认先在顶部引入HttpClient
:
1
| import { HttpClient } from '@angular/common/http';
|
发起get请求:
1 2 3 4 5
| getHeroes(): void { this.http.get('http://localhost:8080/tp-api/public/index.php/hero/index').subscribe(data => { this.heroes = data['result'].list; }); }
|
上面使用get请求从url中获取数据,然后使用箭头函数的回调进行处理。上面将返回数据的result
放在了方括号里面,这是为了防止TS不知道返回数据的属性而报错。HttpClient
将json格式的响应内容解析为了一个对象,但是它并不知道这个对象的形态。我们可以告诉HttpClient
这个响应体类型是什么。
响应体的类型检查
为了告诉HttpClient
响应体的类型是什么,我们需要定义一个接口来描述这个类型的正确形态。
定义响应体的接口:
1 2 3 4 5
| interface HeroResponse { errcode: number; errmsg: string; result: any; }
|
当发起get请求时,传入一个类型参数:
1 2 3 4 5
| getHeroes(): void { this.http.get<HeroResponse>('http://localhost:8080/tp-api/public/index.php/hero/index').subscribe(data => { this.heroes = data.result.list; }); }
|
可以看下get方法的参数定义:
1 2 3 4 5 6 7 8
| get(url: string, options?: { headers?: HttpHeaders; observe?: 'body'; params?: HttpParams; reportProgress?: boolean; responseType?: 'json'; withCredentials?: boolean; }): Observable<Object>;
|
可以自定义一些option参数传过去。
读取完整的响应体
有时候服务器会返回一个特殊的响应头或状态码,已标记出特定的条件,我们怎读取它?我们需要通过observe
选项来告诉HttpClient
,我们需要完整的响应体:
1 2 3 4 5
| getHeroes(): void { this.http.get('http://localhost:8080/tp-api/public/index.php/hero/index', {observe: 'response'}).subscribe(resp => { console.log(resp); }); }
|
这样resp里面包含了请求的所有东西。
错误处理
如果这个请求的目标地址错误怎么办?或者在网络不好的情况下超时了怎么办?HttpClient
就会返回一个错误的响应。我们可以在subscribe()
调用中添加一个错误处理器。
1 2 3 4 5 6 7 8 9
| getHeroes(): void { this.http.get<HeroResponse>('http://localhost:8080/tp-api/public/index.php/hero/index1').subscribe( data => { this.heroes = data.result.list; }, err => { console.log(err); }); }
|
当错误发生的时候,会进入到第二个箭头函数的回调里面去,回调函数的参数err
是一个HttpErrorResponse
类型的对象,它包含了这个错误中一些很有用的信息。
可能发生的错误有两种,一个是后端服务器返回了一个失败的返回码(如404、500、502等),它会返回一个错误。一个是在客户端这边出现了错误(比如在RxJS操作符中抛出的异常或某些阻碍完成这个请求的网络错误),就会抛出一个Error
类型的异常。
解决的方案之一就是简单的重试这次请求,这种策略对临时性的而且不大可能重复发生的错误会很有用。可以使用RxJS中的retry()
操作符来解决这种问题。它会在遇到问题的时候重新订阅这个可观察对象,也就会导致再次发送这个请求。
先导入:
1
| import 'rxjs/add/operator/retry';
|
然后使用在可观察对象上:
1 2 3 4 5 6 7 8 9 10
| getHeroes(): void { this.http.get<HeroResponse>('http://localhost:8080/tp-api/public/index.php/hero/index1').retry(3).subscribe( data => { this.heroes = data.result.list; }, err => { console.log(err); } ); }
|
post请求
常用的操作之一就是使用post请求将数据发送到服务器,比如提交表单。看下这个post请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| getHeroes(): void { this.http.post<HeroResponse>( 'http://localhost:8080/tp-api/public/index.php/hero/index', { id: 3, name: 'tony_post', arr: [ {test: 1}, {test: 2}, ] }, { params: new HttpParams().set('params_id', '3').set('params_name', 'tony') }).subscribe( data => { this.heroes = data.result.list; }, err => { console.log(err); }); }
|
我们在这个post请求里面,加入了请求字符串,我们看看post的参数结构:
1 2 3 4 5 6 7 8
| post(url: string, body: any | null, options?: { headers?: HttpHeaders; observe?: 'body'; params?: HttpParams; reportProgress?: boolean; responseType?: 'json'; withCredentials?: boolean; }): Observable<Object>;
|
可以看到post有三个参数:url
、body
、options
。body
是对象形式,可以传递json形式的复杂数据,而options
里面的params
是键值对形式,会加在url后面。
看来这里很好的处理了在angular1里面使用post传值的时候无法接收到参数的问题。
是时候展示真正的技术了!
掌握了上面的知识我们来开始实践应用。发送请求的频率还是很高的,我们不可能在每个请求里面都重复写下同样的处理,所以我们来构造个请求的api服务。上方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
private requestMethod(url, method, params?) { url = environment.baseUrl + environment.apiUrl + url; let resutObs = null; switch (method) { case 'get': if (params) { params = this.transformRequest(params); } resutObs = this.http.get(url, {responseType: 'json', params: params, withCredentials: true}, ).pipe( catchError(this.handleError) ); break; case 'post': resutObs = this.http.post(url, params, {withCredentials: true}).pipe( catchError(this.handleError) ); break; } return resutObs; }
|
这里写了一个基础方法,在里面发送get请求或者post请求,这里需要注意的是,我们的客户端和服务端是不同的域名,涉及到跨域,所以需要在请求的头部里面带上withCredentials
参数。为了方便我们使用get请求的时候传入json数据,写了个转换参数的方法:
1 2 3 4 5 6 7 8 9 10 11 12
|
private transformRequest(data) { let str = []; for (const s of Object.keys(data)) { str.push(encodeURIComponent(s) + '=' + encodeURIComponent(data[s])); } return str.join('&'); }
|
以及一个错误处理机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
private handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.log('客户端错误:', error.error.message); } else { console.log( `后端错误 错误码为:${error.status} 错误消息:${error.error} `); } return _throw('系统异常,请重试'); }
|
全部代码:
apiUntil.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| import { Injectable } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { _throw } from 'rxjs/observable/throw'; import { catchError } from 'rxjs/operators'; import {environment} from '../../environments/environment';
@Injectable()
export class ApiUntilService {
constructor(private http: HttpClient) {}
public get(url, params?) { return this.requestMethod(url, 'get', params); }
public post(url, params?) { return this.requestMethod(url, 'post', params); }
private requestMethod(url, method, params?) {
url = environment.baseUrl + environment.apiUrl + url;
let resutObs = null;
switch (method) { case 'get': if (params) { params = this.transformRequest(params); } resutObs = this.http.get(url, {responseType: 'json', params: params, withCredentials: true}, ).pipe( catchError(this.handleError) ); break; case 'post': resutObs = this.http.post(url, params, {withCredentials: true}).pipe( catchError(this.handleError) ); break; }
return resutObs; }
private transformRequest(data) { let str = []; for (const s of Object.keys(data)) { str.push(encodeURIComponent(s) + '=' + encodeURIComponent(data[s])); } return str.join('&'); }
private handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.log('客户端错误:', error.error.message); } else { console.log( `后端错误 错误码为:${error.status} 错误消息:${error.error} `); } return _throw('系统异常,请重试'); } }
|
api.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { Injectable } from '@angular/core';
import { ApiUntilService } from './apiUntil.service';
@Injectable()
export class ApiService {
constructor(private apiUnit: ApiUntilService) {
}
login(params?) { return this.apiUnit.post('/login/index', params); } }
|
login.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ApiService } from '../config/api.service'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.less'], providers: [ApiService], }) export class LoginComponent implements OnInit { validateForm: FormGroup; get name() { return this.validateForm.getRawValue().userName; }
get password() { return this.validateForm.getRawValue().password; }
constructor( private api: ApiService, private router: Router, private fb: FormBuilder, ) {
}
ngOnInit() { this.validateForm = this.fb.group({ userName: [ null, [ Validators.required ] ], password: [ null, [ Validators.required ] ], remember: [ true ], });
}
submitForm(): void { this.api.login({ name: this.name, password: this.password, }).subscribe((data) => { if (data.code === 0) { this.router.navigate(['/home', {}]);
} else {
return false; } }); } }
|