引言
創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比騰沖網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式騰沖網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋騰沖地區(qū)。費(fèi)用合理售后完善,十多年實(shí)體公司更值得信賴。
基本上當(dāng)下的應(yīng)用都會(huì)分為前端與后端,當(dāng)然這種前端定義不在限于桌面瀏覽器、手機(jī)、APP等設(shè)備。一個(gè)良好的后端會(huì)通過(guò)一套所有前端都通用的 RESTful API 序列接口作為前后端之間的通信。
這其中對(duì)于身份認(rèn)證都不可能再依賴傳統(tǒng)的Session或Cookie;轉(zhuǎn)而使用諸如OAuth3、JWT等這種更適合API接口的認(rèn)證方式。當(dāng)然本文并不討論如何去構(gòu)建它們。
一、API 設(shè)計(jì)
首先雖然并不會(huì)討論身份認(rèn)證的技術(shù),但不管是OAuth3還是JWT本質(zhì)上身份認(rèn)證都全靠一個(gè) Token 來(lái)維持;因此,下面統(tǒng)一以 token 來(lái)表示身份認(rèn)證所需要的值。
一套合理的API規(guī)則,會(huì)讓前端編碼更優(yōu)雅。因此,希望在編寫Angular之前,能與后端相互達(dá)成一種“協(xié)議”也很有必要??梢試L試從以下幾點(diǎn)進(jìn)行考慮。
版本號(hào)
可以在URL(例:https://demo.com/v1/)或Header(例:headers: { version: 'v1' }
)中體現(xiàn),相比較我更喜歡前者的直接。
業(yè)務(wù)節(jié)點(diǎn)
以一個(gè)節(jié)點(diǎn)來(lái)表示某個(gè)業(yè)務(wù),比如:
動(dòng)作
由HTTP動(dòng)詞來(lái)表示:
統(tǒng)一響應(yīng)
這一點(diǎn)非常重要,特別是當(dāng)我們新建一個(gè)商品時(shí),商品的屬性非常多,但如果我們?nèi)鄙倌硞€(gè)屬性時(shí)??梢允褂眠@樣的一種統(tǒng)一的響應(yīng)格式:
{ "code": 100, // 0 表示成功 "errors": { // 錯(cuò)誤明細(xì) "title": "商品名稱必填" } }
其中 code
不管成功與否都會(huì)有該屬性。
狀態(tài)碼
后端響應(yīng)一個(gè)請(qǐng)求是包括狀態(tài)碼和響應(yīng)內(nèi)容,而每一種狀態(tài)碼又包含著不同的含義。
二、如何訪問(wèn)Http?
首先,需要導(dǎo)入 HttpClientModule 模塊。
import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ HttpClientModule ] })
然后,在組件類注入 HttpClient。
export class IndexComponent { constructor(private http: HttpClient) { } }
最后,請(qǐng)求點(diǎn)擊某個(gè)按鈕發(fā)送一次GET請(qǐng)求。
user: Observable<User>; getUser() { this.user = this.http.get<User>('/assets/data/user.json'); }
打印結(jié)果:
{{ user | async | json }}
三個(gè)簡(jiǎn)單的步驟,就是一個(gè)完整的HTTP請(qǐng)求步驟。
然后,現(xiàn)實(shí)與實(shí)際是有一些距離,比如說(shuō)身份認(rèn)證、錯(cuò)誤處理、狀態(tài)碼處理等問(wèn)題,在上面并無(wú)任何體現(xiàn)。
可,上面已經(jīng)足夠優(yōu)雅,要讓我破壞這種優(yōu)雅那么此文就變得無(wú)意義了!
因此……
三、攔截器
1、HttpInterceptor 接口
正如其名,我們?cè)诓桓淖兩厦鎽?yīng)用層面的代碼下,允許我們把身份認(rèn)證、錯(cuò)誤處理、狀態(tài)碼處理問(wèn)題給解決了!
寫一個(gè)攔截器也是非常的優(yōu)雅,只需要實(shí)現(xiàn) HttpInterceptor
接口即可,而且只有一個(gè) intercept
方法。
@Injectable() export class JWTInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> { // doing } }
intercept
方法有兩個(gè)參數(shù),它幾乎所當(dāng)下流行的中間件概念一般,req
表示當(dāng)前請(qǐng)求數(shù)據(jù)(包括:url、參數(shù)、header等),next
表示調(diào)用下一個(gè)“中間件”。
2、身份認(rèn)證
req
有一個(gè) clone
方法,允許對(duì)當(dāng)前的請(qǐng)求參數(shù)進(jìn)行克隆并且這一過(guò)程會(huì)自行根據(jù)一些參數(shù)推導(dǎo),不管如何用它來(lái)產(chǎn)生一個(gè)新的請(qǐng)求數(shù)據(jù),并在這個(gè)新數(shù)據(jù)中加入我們期望的數(shù)據(jù),比如:token。
const jwtReq = req.clone({ headers: req.headers.set('token', 'xxxxxxxxxxxxxxxxxxxxx') });
當(dāng)然,你可以再折騰更多請(qǐng)求前的一些配置。
最后,把新請(qǐng)求參數(shù)傳遞給下一個(gè)“中間件”。
return next.handle(jwtReq);
等等,都 return
了,說(shuō)好的狀態(tài)碼、異常處理呢?
3、異常處理
仔細(xì)再瞧 next.handle
返回的是一個(gè) Observable
類型??吹?Observable
我們會(huì)想到什么?mergeMap、catch
等一大堆東西。
因此,我們可以利用這些操作符來(lái)改變響應(yīng)的值。
mergeMap
請(qǐng)求過(guò)程中會(huì)會(huì)有一些過(guò)程狀態(tài),比如請(qǐng)求前、上傳進(jìn)度條、請(qǐng)求結(jié)束等,Angular在每一次這類動(dòng)作中都會(huì)觸次 next。因此,我們只需要在返回 Observable
對(duì)象加上 mergeMap
來(lái)觀察這些值的變更,這樣有非常大的自由空間想象。
return next.handle(jwtReq).mergeMap((event: any) => { if (event instanceof HttpResponse && event.body.code !== 0) { return Observable.create(observer => observer.error(event)); } return Observable.create(observer => observer.next(event)); })
只會(huì)在請(qǐng)求成功才會(huì)返回一個(gè) HttpResponse 類型,因此,我們可以大膽判斷是否來(lái)源于 HttpResponse 來(lái)表示HTTP請(qǐng)求已經(jīng)成功。
這里,統(tǒng)一對(duì)業(yè)務(wù)層級(jí)的錯(cuò)誤 code !== 0 產(chǎn)生一個(gè)錯(cuò)誤信號(hào)的 Observable。反之,產(chǎn)生一個(gè)成功的信息。
catch
catch 來(lái)捕獲非200以外的其他狀態(tài)碼的錯(cuò)誤,比如:401。同時(shí),前面的 mergeMap 所產(chǎn)生的錯(cuò)誤信號(hào),也會(huì)在這里被捕獲到。
.catch((res: HttpResponse<any>) => { switch (res.status) { case 401: // 權(quán)限處理 location.href = ''; // 重新登錄 break; case 200: // 業(yè)務(wù)層級(jí)錯(cuò)誤處理 alert('業(yè)務(wù)錯(cuò)誤:' + res.body.code); break; case 404: alert('API不存在'); break; } return Observable.throw(res); })
4、完整代碼
至此,攔截器所要包括的身份認(rèn)證token、統(tǒng)一響應(yīng)處理、異常處理都解決了。
@Injectable() export class JWTInterceptor implements HttpInterceptor { constructor(private notifySrv: NotifyService) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> { console.log('interceptor') const jwtReq = req.clone({ headers: req.headers.set('token', 'asdf') }); return next .handle(jwtReq) .mergeMap((event: any) => { if (event instanceof HttpResponse && event.body.code !== 0) { return Observable.create(observer => observer.error(event)); } return Observable.create(observer => observer.next(event)); }) .catch((res: HttpResponse<any>) => { switch (res.status) { case 401: // 權(quán)限處理 location.href = ''; // 重新登錄 break; case 200: // 業(yè)務(wù)層級(jí)錯(cuò)誤處理 this.notifySrv.error('業(yè)務(wù)錯(cuò)誤', `錯(cuò)誤代碼為:${res.body.code}`); break; case 404: this.notifySrv.error('404', `API不存在`); break; } // 以錯(cuò)誤的形式結(jié)束本次請(qǐng)求 return Observable.throw(res); }) } }
發(fā)現(xiàn)沒(méi)有,我們并沒(méi)有加一大堆并不認(rèn)識(shí)的事物,單純都只是對(duì)數(shù)據(jù)流的各種操作而已。
NotifyService 是一個(gè)無(wú)須依賴HTML模板、極簡(jiǎn)Angular通知組件。
5、注冊(cè)攔截器
攔截器構(gòu)建后,還需要將其注冊(cè)至 HTTP_INTERCEPTORS 標(biāo)識(shí)符中。
import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ HttpClientModule ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true} ] })
以上是攔截器的所有內(nèi)容,在不改變?cè)械拇a的情況下,我們只是利用短短幾行的代碼實(shí)現(xiàn)了身份認(rèn)證所需要的TOKEN、業(yè)務(wù)級(jí)統(tǒng)一響應(yīng)處理、錯(cuò)誤處理動(dòng)作。
四、async 管道
一個(gè) Observable
必須被訂閱以后才會(huì)真正的開始動(dòng)作,前面在HTML模板中我們利用了 async
管道簡(jiǎn)化了這種訂閱過(guò)程。
{{ user | async | json }}
它相當(dāng)于:
let user: User; get() { this.http.get<User>('/assets/data/user.json').subscribe(res => { this.user = res; }); } {{ user | json }}
然而,async 這種簡(jiǎn)化,并不代表失去某些自由度,比如說(shuō)當(dāng)在獲取數(shù)據(jù)過(guò)程中顯示【加載中……】,怎么辦?
<div *ngIf="user | async as user; else loading"> {{ user | json }} </div> <ng-template #loading>加載中……</ng-template>
恩!
五、結(jié)論
Angular在HTTP請(qǐng)求過(guò)程中使用 Observable 異步數(shù)據(jù)流控制數(shù)據(jù),而利用 rxjs 提供的大量操作符,來(lái)改變最終值;從而獲得在應(yīng)用層面最優(yōu)雅的編碼風(fēng)格。
當(dāng)我們說(shuō)到優(yōu)雅使用HTTP這件事時(shí),易測(cè)試是一個(gè)非常重要,因此,我建議將HTTP從組件類中剝離并將所有請(qǐng)求放到 Service 當(dāng)中。當(dāng)對(duì)某個(gè)組件編寫測(cè)試代碼時(shí),如果受到HTTP請(qǐng)求結(jié)果的限制會(huì)讓測(cè)試更困難。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
分享文章:詳解如何在Angular優(yōu)雅編寫HTTP請(qǐng)求
URL地址:http://m.rwnh.cn/article14/jejgde.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)、微信公眾號(hào)、、企業(yè)網(wǎng)站制作、用戶體驗(yàn)、網(wǎng)站策劃
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)