在Angular網絡請求是一個最多見的應用之一,下列我將以 ng-alain 項目爲基礎描述 Angular 網絡請求。html
注:示例中代碼都以簡化的形式出現。
Angular發起一個請求再簡單不過即便用 HttpClient
類的各類方法,然在開始以前咱們應退一小步,先從如何構建一個 Restful API 開始,後端的API設計將很大程度決定先後端如何更優雅的開發有着很是大的關鍵性做用。前端
私覺得API的設計分爲請求與輸出兩個部分。而鏈接兩者是依靠URL,關於URL如何更合理的設計能夠參考阮一峯-RESTful API 設計指南。git
這一部分要談另外一個可能你們容易忽略的細節,請求體與返回體規範。這一點淘寶開放平臺是一個很是好的典範,例如全部異常返回體:github
{ "sub_msg":"非法參數", "code":50, "sub_code":"isv.invalid-parameter", "msg":"Remote service error" }
全部這些規則能夠由內部自行決議,再好比咱們中後臺常用的是一種方式,全部返回體無論成功與否都包含如下對象:json
{ "msg": "ok", "data": null }
以 msg
來判斷 ok
值表示成功,對於其餘值表示容許直接顯示給用戶錯誤文本異常文本。後端
對於提交 POST
請求體的數據格式(content-type
)主要兩種比較常見:表單格式和JSON格式,兩者也可能根據不一樣場景狀況使用特別是文件上傳動做;固然對於大部分場景而言 JSON 格式最優先的形式,無論你是使用 Angular 表單的HTML模板或響應式驅動表單都是直接跟JSON打交道。api
在 ng-alain 中,一個完整的 Angular 應用從前端 UI 交互到服務端處理流程是這樣的:restful
一、首次啓動 Angular 執行 APP_INITIALIZER
;
二、UI 組件交互操做;
三、使用 HttpClient
發送請求;
四、觸發用戶認證攔截器 @delon/auth
,統一加入 token
參數;網絡
a、若未存在 `token` 或已過時中斷後續請求,直接跳轉至登陸頁;
五、觸發默認攔截器,統一處理前綴等信息;
六、獲取服務端返回;
七、觸發默認攔截器,統一處理請求異常、業務異常等;
八、數據更新,並刷新 UI。app
本文咱們不介紹渲染方面,所以 2,6,8 三點將不作介紹。
應用初始化是在應用啓動過程當中有且只執行一次,通常來說咱們須要在應用一啓動時加載一些數據:應用信息、通用數據字典、用戶數據等。
只須要向 APP_INITIALIZER
註冊一個帶有 Promise
返回值便可;例如:
{ provide: APP_INITIALIZER, useValue: () => new Promise(() => {}), multi: true }
正由於是一個 Promise
異步,咱們就能夠在這裏利用 HttpClient
作網絡請求,從而實如今 Angular 啓動以前經過網絡請求獲取一個啓用後一開始就須要的數據。
注:固然在這裏發起的網絡請求攔截器依然有效,若攔截器包含一些用戶 Token 的有效性校驗而致使跳轉至登陸頁時,可能要當心處理了。
但無論如何最終你想啓動 Angular 都必須確保 Promise
正確的調用 resolve()
。
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
。
攔截網絡請求或響應,用於統一處理請求或響應結果數據。而且能夠運用多個攔截器且按順序執行,相似於 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 默認裝載了兩個攔截器:@delon/auth 用戶認證和默認攔截器。
自己是爲 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 請求來決定是否是真的可使用。
有關更多細節請參考文檔。
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 網絡請求又那麼清晰。
(完)