淺談Angular網路請求
來自專欄 ng-alain25 人贊了文章
在Angular網路請求是一個最常見的應用之一,下列我將以 ng-alain 項目為基礎描述 Angular 網路請求。
註:示例中代碼都以簡化的形式出現。
寫在前面
Angular發起一個請求再簡單不過即使用 HttpClient
類的各種方法,然在開始之前我們應退一小步,先從如何構建一個 Restful API 開始,後端的API設計將很大程度決定前後端如何更優雅的開發有著非常大的關鍵性作用。
一、RESTful API 設計
私以為API的設計分為請求與輸出兩個部分。而連接二者是依靠URL,關於URL如何更合理的設計可以參考阮一峰-RESTful API 設計指南。
這一部分要談另一個可能大家容易忽略的細節,請求體與返回體規範。這一點淘寶開放平台是一個非常好的典範,例如所有異常返回體:
{ "sub_msg":"非法參數", "code":50, "sub_code":"isv.invalid-parameter", "msg":"Remote service error"}
所有這些規則可以由內部自行決議,再比如我們中後台經常使用的是一種方式,所有返回體不管成功與否都包含以下對象:
{ "msg": "ok", "data": null}
以 msg
來判斷 ok
值表示成功,對於其他值表示允許直接顯示給用戶錯誤文本異常文本。
對於提交 POST
請求體的數據格式(content-type
)主要兩種比較常見:表單格式和JSON格式,二者也可能根據不同場景情況使用特別是文件上傳動作;當然對於大部分場景而言 JSON 格式最優先的形式,不管你是使用 Angular 表單的HTML模板或響應式驅動表單都是直接跟JSON打交道。
二、請求流程
在 ng-alain 中,一個完整的 Angular 應用從前端 UI 交互到服務端處理流程是這樣的:
1、首次啟動 Angular 執行 APP_INITIALIZER
;
HttpClient
發送請求;4、觸發用戶認證攔截器 @delon/auth
,統一加入 token
參數; a、若未存在 token
或已過期中斷後續請求,直接跳轉至登錄頁;5、觸發默認攔截器,統一處理前綴等信息;6、獲取服務端返回;
7、觸發默認攔截器,統一處理請求異常、業務異常等;
8、數據更新,並刷新 UI。本文我們不介紹渲染方面,因此 2,6,8 三點將不做介紹。
1、APP_INITIALIZER
應用初始化是在應用啟動過程中有且只執行一次,一般來講我們需要在應用一啟動時載入一些數據:應用信息、通用數據字典、用戶數據等。
只需要向 APP_INITIALIZER
註冊一個帶有 Promise
返回值即可;例如:
{ provide: APP_INITIALIZER, useValue: () => new Promise(() => {}), multi: true}
正因為是一個 Promise
非同步,我們就可以在這裡利用 HttpClient
做網路請求,從而實現在 Angular 啟動之前通過網路請求獲取一個啟用後一開始就需要的數據。
註:當然在這裡發起的網路請求攔截器依然有效,若攔截器包含一些用戶 Token 的有效性校驗而導致跳轉至登錄頁時,可能要小心處理了。
但不管如何最終你想啟動 Angular 都必須確保 Promise
正確的調用 resolve()
。
2、HttpClient
HttpClient 是 Angular 封裝了一個簡化的 API 來實現 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); });}
所有請求類型返回的結果都是 Observable<any>
類型,意味著不管如果你都必須調用 subscribe
才會真正的發起請求。大多數情況下你可能會覺得很麻煩,但當你需要一些節流或數據轉換時就顯得 rxjs
的魅力,有關更多細節自行Google rxjs
。
3、攔截器
攔截網路請求或響應,用於統一處理請求或響應結果數據。並且可以運用多個攔截器且按順序執行,類似於 Node 中間件。
一個簡單示例
只需要簡單實現 HttpInterceptor
介面即可:
export class SimpleInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> { const newReq = req.clone(); return next.handle(newReq).pipe() }}
攔截器返回的結果是一個 Observable
值,這意味著同一個攔截器代碼包含著請求和響應兩個部分的處理,所有在 Angular 攔截器里並沒有明確區分請求和響應處理,這也是 rxjs
的魅力。
使用 req.clone()
克隆一些新的請求體,當然請求體包含著所有 HttpClient
發起數據及參數。例如給所有請求體的 headers
加入用戶 Token 值。
const newReq = req.clone({ setHeaders: { Authorization: `Bearer ${this.token}` },});
當響應體網路狀態碼非 401
時,打算跳轉至登錄頁,則:
return next.handle(newReq) .pipe( catchError(err => { if (err.status === 401) { this.injector.get(Router).navigateByUrl(/login); } }) )
最後,在模塊里註冊,若你希望在整個應用有效可以在根模塊里註冊:
{ provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true },
攔截器順序
攔截器可以註冊在任何模塊里,而一個網路請求所經過攔截器從模塊向上查找至根模塊,若一個模塊包含多個攔截器時按代碼順序執行。
三、ng-alain 請求處理
ng-alain 默認裝載了兩個攔截器:@delon/auth 用戶認證和默認攔截器。
1、用戶認證
本身是為 ng-alain 腳手架提供的一個用戶認證模塊,包含主流的 JWT(Json Web Token)和一個相對通用 Simple Web Token,而其核心是對認證過程進一步處理。而通常其核心在於用戶 Token 的獲取、使用環節。
同時,@delon/auth
並不會關心用戶界面是怎麼樣,只需要當登錄成功後將後端返回的數據交給ITokenService
,它會幫你存儲在localStorage
(默認) 當中;當發起一個網路請求時,它會在自動在header
(默認) 當中加入相應的 token 信息。因此,
@delon/auth
不限於 ng-alain 腳手架,任何 Angular 項目都可以使用它。
默認裝載了 SimpleInterceptor
攔截器,意味者一開始使用 ng-alain 為什麼會無緣無故無法正確請求,而是直接拋出異常。
ng-alain 是一個完整且可直接運用項目的腳手架,因此所有默認配置都儘可能生產環境中代碼,其實理解這一點很重要,因為大部分一開始總希望使用一個 Hello World 請求來決定是不是真的可以使用。
有關更多細節請參考文檔。
2、默認攔截器
DefaultInterceptor
攔截器,它是一個默認攔截器示例代碼,包含請求體和響應體的處理。
例如當我們統一響應體如下:
{ "msg": "ok", "data": { id: 1, name: "cipchk" }}
對於 subscribe
結果來說只需要關心 data
部分,因此可以在攔截器進一步轉化:
return of(new HttpResponse(Object.assign(event, { body: body.data })));
使在訂閱結果時給保持一個最簡單有效數據:
http.get(/user/1).subscribe(user => console.log(user));// output: { id: 1, name: "cipchk" }
更多做法,例如:統一處理異常消息等,可以參考 default.interceptor.ts 的寫法。
總結
Angular 網路請求看起來就像一個簡化版的 Web 服務,發起的請求經過一道道關卡後,接收響應結果時又經過原先經過的一道道關卡最後交給用戶。
當然這一切的本質還是 rxjs 帶來的。曾經有人提過為什麼 ng-alain 不採用 Redux 形式,但我實在找不到有什麼理由要這麼做,大部分中後台都以網路請求來完成大部分事務,而 Angular 網路請求又那麼清晰。
(完)
推薦閱讀:
※搭建Angular開發環境
※前端日刊-2018.04.04
※Angular2以上項目中使用jquery插件
※使用 render props 抽象 Modal 組件的狀態