浅谈Angular网络请求
在Angular網(wǎng)絡(luò)請求是一個最常見的應(yīng)用之一,下列我將以 ng-alain 項(xiàng)目為基礎(chǔ)描述 Angular 網(wǎng)絡(luò)請求。
注:示例中代碼都以簡化的形式出現(xiàn)。寫在前面
Angular發(fā)起一個請求再簡單不過即使用 HttpClient 類的各種方法,然在開始之前我們應(yīng)退一小步,先從如何構(gòu)建一個 Restful API 開始,后端的API設(shè)計(jì)將很大程度決定前后端如何更優(yōu)雅的開發(fā)有著非常大的關(guān)鍵性作用。
一、RESTful API 設(shè)計(jì)
私以為API的設(shè)計(jì)分為請求與輸出兩個部分。而連接二者是依靠URL,關(guān)于URL如何更合理的設(shè)計(jì)可以參考阮一峰-RESTful API 設(shè)計(jì)指南。
這一部分要談另一個可能大家容易忽略的細(xì)節(jié),請求體與返回體規(guī)范。這一點(diǎn)淘寶開放平臺是一個非常好的典范,例如所有異常返回體:
{"sub_msg":"非法參數(shù)","code":50,"sub_code":"isv.invalid-parameter","msg":"Remote service error" }所有這些規(guī)則可以由內(nèi)部自行決議,再比如我們中后臺經(jīng)常使用的是一種方式,所有返回體不管成功與否都包含以下對象:
{"msg": "ok","data": null }以 msg 來判斷 ok 值表示成功,對于其他值表示允許直接顯示給用戶錯誤文本異常文本。
對于提交 POST 請求體的數(shù)據(jù)格式(content-type)主要兩種比較常見:表單格式和JSON格式,二者也可能根據(jù)不同場景情況使用特別是文件上傳動作;當(dāng)然對于大部分場景而言 JSON 格式最優(yōu)先的形式,不管你是使用 Angular 表單的HTML模板或響應(yīng)式驅(qū)動表單都是直接跟JSON打交道。
二、請求流程
在 ng-alain 中,一個完整的 Angular 應(yīng)用從前端 UI 交互到服務(wù)端處理流程是這樣的:
1、首次啟動 Angular 執(zhí)行 APP_INITIALIZER;
2、UI 組件交互操作;
3、使用 HttpClient 發(fā)送請求;
4、觸發(fā)用戶認(rèn)證攔截器 @delon/auth,統(tǒng)一加入 token 參數(shù);
5、觸發(fā)默認(rèn)攔截器,統(tǒng)一處理前綴等信息;
6、獲取服務(wù)端返回;
7、觸發(fā)默認(rèn)攔截器,統(tǒng)一處理請求異常、業(yè)務(wù)異常等;
8、數(shù)據(jù)更新,并刷新 UI。
1、APP_INITIALIZER
應(yīng)用初始化是在應(yīng)用啟動過程中有且只執(zhí)行一次,一般來講我們需要在應(yīng)用一啟動時(shí)加載一些數(shù)據(jù):應(yīng)用信息、通用數(shù)據(jù)字典、用戶數(shù)據(jù)等。
只需要向 APP_INITIALIZER 注冊一個帶有 Promise 返回值即可;例如:
{provide: APP_INITIALIZER,useValue: () => new Promise(() => {}),multi: true }正因?yàn)槭且粋€ Promise 異步,我們就可以在這里利用 HttpClient 做網(wǎng)絡(luò)請求,從而實(shí)現(xiàn)在 Angular 啟動之前通過網(wǎng)絡(luò)請求獲取一個啟用后一開始就需要的數(shù)據(jù)。
注:當(dāng)然在這里發(fā)起的網(wǎng)絡(luò)請求攔截器依然有效,若攔截器包含一些用戶 Token 的有效性校驗(yàn)而導(dǎo)致跳轉(zhuǎn)至登錄頁時(shí),可能要小心處理了。
但不管如何最終你想啟動 Angular 都必須確保 Promise 正確的調(diào)用 resolve()。
2、HttpClient
HttpClient 是 Angular 封裝了一個簡化的 API 來實(shí)現(xiàn) HTTP 客戶端功能,例如一個 get 請求:
constructor(http: HttpClient) {http.get('/user/1').subscribe((user) => {console.log(user);}); }另一個 post 請求:
constructor(http: HttpClient) {http.post('/user/1', { a: 1 }).subscribe((user) => {console.log(user);}); }所有請求類型返回的結(jié)果都是 Observable<any> 類型,意味著不管如果你都必須調(diào)用 subscribe 才會真正的發(fā)起請求。大多數(shù)情況下你可能會覺得很麻煩,但當(dāng)你需要一些節(jié)流或數(shù)據(jù)轉(zhuǎn)換時(shí)就顯得 rxjs 的魅力,有關(guān)更多細(xì)節(jié)自行Google rxjs。
3、攔截器
攔截網(wǎng)絡(luò)請求或響應(yīng),用于統(tǒng)一處理請求或響應(yīng)結(jié)果數(shù)據(jù)。并且可以運(yùn)用多個攔截器且按順序執(zhí)行,類似于 Node 中間件。
一個簡單示例
只需要簡單實(shí)現(xiàn) HttpInterceptor 接口即可:
export class SimpleInterceptor implements HttpInterceptor {intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {const newReq = req.clone();return next.handle(newReq).pipe()} }攔截器返回的結(jié)果是一個 Observable 值,這意味著同一個攔截器代碼包含著請求和響應(yīng)兩個部分的處理,所有在 Angular 攔截器里并沒有明確區(qū)分請求和響應(yīng)處理,這也是 rxjs 的魅力。
使用 req.clone() 克隆一些新的請求體,當(dāng)然請求體包含著所有 HttpClient 發(fā)起數(shù)據(jù)及參數(shù)。例如給所有請求體的 headers 加入用戶 Token 值。
const newReq = req.clone({setHeaders: { Authorization: `Bearer ${this.token}` }, });當(dāng)響應(yīng)體網(wǎng)絡(luò)狀態(tài)碼非 401 時(shí),打算跳轉(zhuǎn)至登錄頁,則:
return next.handle(newReq).pipe(catchError(err => {if (err.status === 401) {this.injector.get(Router).navigateByUrl('/login');}}))最后,在模塊里注冊,若你希望在整個應(yīng)用有效可以在根模塊里注冊:
{ provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true },攔截器順序
攔截器可以注冊在任何模塊里,而一個網(wǎng)絡(luò)請求所經(jīng)過攔截器從模塊向上查找至根模塊,若一個模塊包含多個攔截器時(shí)按代碼順序執(zhí)行。
三、ng-alain 請求處理
ng-alain 默認(rèn)裝載了兩個攔截器:@delon/auth 用戶認(rèn)證和默認(rèn)攔截器。
1、用戶認(rèn)證
本身是為 ng-alain 腳手架提供的一個用戶認(rèn)證模塊,包含主流的 JWT(Json Web Token)和一個相對通用 Simple Web Token,而其核心是對認(rèn)證過程進(jìn)一步處理。而通常其核心在于用戶 Token 的獲取、使用環(huán)節(jié)。同時(shí),@delon/auth 并不會關(guān)心用戶界面是怎么樣,只需要當(dāng)?shù)卿洺晒髮⒑蠖朔祷氐臄?shù)據(jù)交給 ITokenService,它會幫你存儲在 localStorage(默認(rèn)) 當(dāng)中;當(dāng)發(fā)起一個網(wǎng)絡(luò)請求時(shí),它會在自動在 header(默認(rèn)) 當(dāng)中加入相應(yīng)的 token 信息。
因此,@delon/auth 不限于 ng-alain 腳手架,任何 Angular 項(xiàng)目都可以使用它。
默認(rèn)裝載了 SimpleInterceptor 攔截器,意味者一開始使用 ng-alain 為什么會無緣無故無法正確請求,而是直接拋出異常。
ng-alain 是一個完整且可直接運(yùn)用項(xiàng)目的腳手架,因此所有默認(rèn)配置都盡可能生產(chǎn)環(huán)境中代碼,其實(shí)理解這一點(diǎn)很重要,因?yàn)榇蟛糠忠婚_始總希望使用一個 Hello World 請求來決定是不是真的可以使用。
有關(guān)更多細(xì)節(jié)請參考文檔。
2、默認(rèn)攔截器
DefaultInterceptor 攔截器,它是一個默認(rèn)攔截器示例代碼,包含請求體和響應(yīng)體的處理。
例如當(dāng)我們統(tǒng)一響應(yīng)體如下:
{"msg": "ok","data": { id: 1, name: "cipchk" } }對于 subscribe 結(jié)果來說只需要關(guān)心 data 部分,因此可以在攔截器進(jìn)一步轉(zhuǎn)化:
return of(new HttpResponse(Object.assign(event, { body: body.data })));使在訂閱結(jié)果時(shí)給保持一個最簡單有效數(shù)據(jù):
http.get('/user/1').subscribe(user => console.log(user)); // output: { id: 1, name: "cipchk" }更多做法,例如:統(tǒng)一處理異常消息等,可以參考 default.interceptor.ts 的寫法。
總結(jié)
Angular 網(wǎng)絡(luò)請求看起來就像一個簡化版的 Web 服務(wù),發(fā)起的請求經(jīng)過一道道關(guān)卡后,接收響應(yīng)結(jié)果時(shí)又經(jīng)過原先經(jīng)過的一道道關(guān)卡最后交給用戶。
當(dāng)然這一切的本質(zhì)還是 rxjs 帶來的。曾經(jīng)有人提過為什么 ng-alain 不采用 Redux 形式,但我實(shí)在找不到有什么理由要這么做,大部分中后臺都以網(wǎng)絡(luò)請求來完成大部分事務(wù),而 Angular 網(wǎng)絡(luò)請求又那么清晰。
(完)
總結(jié)
以上是生活随笔為你收集整理的浅谈Angular网络请求的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 带你一起撸一遍 nodejs 常用核心模
- 下一篇: Java 集合系列(三)Collecti