淺談Angular網路請求
在Angular網路請求是一個最常見的應用之一,下列我將以ng-alain專案為基礎描述 Angular 網路請求。
注:示例中程式碼都以簡化的形式出現。
寫在前面
Angular發起一個請求再簡單不過即使用HttpClient
類的各種方法,然在開始之前我們應退一小步,先從如何構建一個 Restful API 開始,後端的API設計將很大程度決定前後端如何更優雅的開發有著非常大的關鍵性作用。
一、RESTful API 設計
私以為API的設計分為請求與輸出兩個部分。而連線二者是依靠URL,關於URL如何更合理的設計可以參考
這一部分要談另一個可能大家容易忽略的細節,請求體與返回體規範。這一點淘寶開放平臺是一個非常好的典範,例如所有異常返回體:
{
"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
;
2、UI 元件互動操作;
3、使用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
的魅力,有關更多細節自行Googlerxjs
。
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
部分,因此可以在攔截器進一步轉化:
returnof(newHttpResponse(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 網路請求又那麼清晰。
轉載於:https://my.oschina.net/jack088/blog/3029163