准备工作
ng g m http-study
ng g c http-study -c OnPush -t -s
ng g s http-study/http-study
HttpStudyService
:// http-study.component.ts import {HttpStudyService} from './http-study.service'; ... constructor(private httpStudyServer: HttpStudyService) { } ...
HttpClient
HttpClient
提供各种向服务器请求数据的方法,返回一个可观察对象 Observable<any>
。详见官网文档使用步骤
AppModule
中或者其他你想要的地方导入 HttpClient
服务类;import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ BrowserModule, HttpClientModule, ], ... })
HttpClient
服务注入成 HttpStudyService
的依赖项;// http-study.service.ts import {HttpClient} from '@angular/common/http'; @Injectable() export class HttpStudyService { constructor(private http: HttpClient) { } }
HttpClient
的方法从服务器请求数据。this.http.get('http://localhost:3333/hero/list');
解决本地开发跨域问题
3333
端口,但是,我们的程序在 4200
端口。直接请求数据,毫无疑问会有跨域的问题。- 在项目的
src/
目录下创建一个proxy.conf.json
文件。 - 往这个新的代理配置文件中添加如下内容:
{ "/api": { "target": "http://localhost:3333", "secure": false, "pathRewrite": { "^/api": "" } } }
tips:这个文件表示:当你访问 '/api'
开头的路径时,将会实际访问 //localhost:3333/api
,但是接口是不带有 'api'
的,所以需要 pathRewrite
,在实际请求时将 '/api'
替换成空。
- 在
CLI
配置文件angular.json
中为serve
目标添加proxyConfig
选项:
... "architect": { "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "hero:build", "proxyConfig": "src/proxy.conf.json" }, ...
- 要使用这个代理选项启动开发服务器,或修改了代理配置,都应该重启环境。
如果你要访问的后端服务器不在 localhost
上,还要设置 changeOrigin
选项:
{ "/api": { "target": "http://localhost:3333", "secure": false, "pathRewrite": { "^/api": "" }, "changeOrigin": true } }
这样,我们就能以 '/api/hero/list'
这样的路径访问到本地跨域的服务器了。
但是又出现了另一个问题,在本地开发中我们给路径添加了 '/api'
,但是线上环境可能不需要这个啊。所以,我们需要区别程序运行环境来动态添加路径。
配置应用环境
在我们使用 cli
创建应用的时候,就自动生成了一个 environments
文件夹,里面的两个文件就包含了两个基础的应用环境配置。
environment.ts
包含了默认的环境设置。当没有指定环境时,build
命令就会用它作为构建目标。// environment.ts export const environment = { production: false };
environment.prod.ts
里面的配置进行构建。// environment.prod.ts export const environment = { production: true };
angular.json
中的每个构建目标下都包含了一个 fileReplacements
字段。这能让你把任何文件替换为针对特定目标的版本。这表示在运行 用 ng build --prod
或 ng build --configuration=production
命令时用 environment.prod.ts
替换 environment.ts
。
基于上面的原理,我们就可以根据环境配置 http
请求的一个基础路径。
/api
开头:// environment.ts export const environment = { production: false, baseUrl: '/api' };
// environment.prod.ts export const environment = { production: true, baseUrl: '' };
// http-study.service.ts import {environment} from '../../environments/environment'; ... this.http.get(environment.baseUrl + '/hero/list'); ...
url
始终是真实的路径。get() 方法
Observable
,当它被订阅时,会要求服务器执行配置好的 GET
请求。get(url: string[, options: object]): Observable<any>
options: { headers?: HttpHeaders | {[header: string]: string | string[]}, observe?: 'body' | 'events' | 'response', params?: HttpParams|{[param: string]: string | string[]}, reportProgress?: boolean, responseType?: 'arraybuffer'|'blob'|'json'|'text', withCredentials?: boolean, }
params
参数,接受 HttpParams
类型或者键值对形式的对象。来看 HttpParams
:HttpParams类
用来表示序列化参数,它们的 MIME
类型都是 application/x-www-form-urlencoded
。
class HttpParams { constructor(options: HttpParamsOptions = {} as HttpParamsOptions) has(param: string): boolean get(param: string): string | null getAll(param: string): string[] | null keys(): string[] append(param: string, value: string): HttpParams set(param: string, value: string): HttpParams delete(param: string, value?: string): HttpParams toString(): string }
使用 HttpParams
来序列化参数,获取英雄列表:
// http-study.service.ts private prefix = environment.baseUrl + '/hero/'; getList(): Observable<Hero[]> { return this.http.get(this.prefix + 'list').pipe( map((res: Base<Hero[]>) => res.data) ); }
调用方法时一定要订阅才会发送请求:
// http-study.component.ts getList(): void { this.httpStudyServer.getList().subscribe(data => { console.log(data); }); }
这样,我们就能获得全部的英雄列表。
当然,我们也可以使用 set()
/ append()
方法传参:
// http-study.service.ts getList(): Observable<Hero[]> { const params = new HttpParams().set('name', '卡特').set('sort', 'desc'); // const params = new HttpParams().append('name', '卡特').append('sort', 'desc'); return this.http.get(this.prefix + 'list', {params}).pipe( map((res: Base<Hero[]>) => res.data) ); }
他们两者之间的区别是: set()
是替换参数值, append()
是追加参数。其他方法也能见名知意,就不赘述。
但是这样的形式进行传参未免太繁杂,我们也可以使用 fromString
变量从查询字符串中直接创建 HTTP
参数:
const params = new HttpParams({fromString: 'name=卡特'});
这里,为了转换对象为 'name=卡特' 形式,我们需要引入一个方法:
// http-study.service.ts import {stringify} from 'querystring'; getList(args: HeroArg): Observable<Hero[]> { const params = new HttpParams({fromString: stringify(args)}); return this.http.get(this.prefix + 'list', {params}).pipe( map((res: Base<Hero[]>) => res.data) ); }
调用时传入参数:
// http-study.component.ts getList(): void { this.httpStudyServer.getList({name: '卡特', job: '', sort: 'desc'}).subscribe(data => { console.log(data); }); }
处理请求错误
程序中发生错误通常会有两种情况:
- 服务器端发生错误,可能是服务器拒绝访问或直接报错;
- 客户端发生错误,可能网络错误什么的造成的。
针对不同的错误类型,我们需要定制不同的错误处理逻辑。
// http-study.service.ts getList(args: HeroArg): Observable<Hero[]> { const params = new HttpParams({fromString: stringify(args)}); return this.http.get(this.prefix + 'list', {params}).pipe( map((res: Base<Hero[]>) => res.data), // 抛出错误 catchError(this.handleError) ); } private handleError(error: HttpErrorResponse) { // 根据自己项目实际情况判定 if (typeof error.error?.code === 'number') { console.error(`服务器端发生错误,状态码:${error.error.code}`); } else { console.error('请求失败'); } return throwError(error); }
这样在服务中制定错误处理逻辑可以方便复用代码,当然,我们也可以在调用接口时使用 subscribe
的第二个参数进行定制专属错误提示。
RxJs
的重试操作符 retry
来重新发送请求:// http-study.service.ts getList(args: HeroArg): Observable<Hero[]> { const params = new HttpParams({fromString: stringify(args)}); return this.http.get(this.prefix + 'list', {params}).pipe( map((res: Base<Hero[]>) => res.data), retry(3), // 重试3次,一共会发送4次请求 catchError(this.handleError) ); }
添加请求头
设置请求头也是日常开发中常见的操作。Angular
提供了 HttpHeaders
类来配置请求头。
里面的方法跟 HttpParams
类似,set()
方法用于添加或更新:
// http-study.service.ts getList(args: HeroArg): Observable<Hero[]> { const params = new HttpParams({fromString: stringify(args)}); // 定义请求头 const headers = new HttpHeaders({token: 'my-auth-token'}).set('self-header', 'test'); return this.http.get(this.prefix + 'list', {params, headers}).pipe( // ... ); }
对于其他的请求方式这里就不打算介绍了,大同小异,需要的时候看文档就够
作为基础章节,我们就介绍这么多。下一节将继续介绍 http
的进阶使用,敬请期待~
最新评论