前端應用都需要通過 HTTP 協議與后端服務器通訊,@angular/common/http
中的 HttpClient
類為 Angular 應用程序提供的 API 來實現 HTTP 客戶端功能。它基於瀏覽器提供的 XMLHttpRequest
接口。 HttpClient
帶來的其它優點包括:可測試性、強類型的請求和響應對象、發起請求與接收響應時的攔截器支持,以及更好的、基於可觀察(Observable)對象的 API 以及流式錯誤處理機制。
1.使用http服務
在根模塊AppModule
導入HttpClientModule。因為大多數應用中,多處多都要使用到http服務,所以直接導入到根模塊去。
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ BrowserModule, HttpClientModule, ], declarations: [ AppComponent, ], bootstrap: [ AppComponent ] }) export class AppModule {}
然后就可以在服務中使用http服務了
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() export class UserService { constructor(private http: HttpClient) { }
getUsers():Observable<Array<userInfo>>{
return this.http.get<Array<userInfo>>(url);
} }
2.封裝httpService
通常一個應用我們不會直接使用HttpClient,數據的訪問沒有這么簡單,我們可能會對header 有統一的添加,錯誤處理,數據處理,網絡異常等情況處理。如果不封裝一個服務那么我們直接使用HttpClient,會出現大量的重復,冗余雜亂的代碼,無法統一標准化,在長期的開發以及測試中會變得麻煩和增加維護成本。這就是為什么在一個應用程序中,無論項目多小,我們都要把數據訪問封裝一個單獨的服務中去的原因。
這個是HttpClient.post 方法,我們可以看到他的返回類型是某個值的 RxJS Observable。
默認情況下把響應體當做無類型的 JSON 對象進行返回,如果指定了模版類型Array<userInfo>,他就會返回一個此類型的對象回來,但是具體還是得取決於數據接口返回來的數據。
參考上面的UserService服務里的getUsers。下面是某個組件調用。
users: Array<User>; showUser() { this.userService.getUsers() .subscribe((data:Array<User>) => this.users = { ...data }); }
返回數據結果統一處理
通常我們API返回來的數據結構是統一的規范化結果,有利於項目前后端分離,團隊合作開發。
export class ResultData<T> { success: boolean; msg?: string; data?: T; errorcode?: string; timestamp?: number; }
例子在下面👇
錯誤處理
當你發起一個網絡請求,你不知道服務器會給你返回什么,你不知道請求是否能到達服務器,會不會出現網絡堵塞,服務器錯誤等等等的問題,這時候就需要要捕獲錯誤,並對錯誤進行處理。使用 RxJS 的 catchError() 操作符來建立對 Observable 結果的處理管道(pipe)
//post 請求
public post(url: string, data = {}): Observable<any> { return this.http.post(url, data, httpOptions).pipe( map(this.extractData), catchError(this.handleError)//獲取了由 HttpClient 方法返回的 Observable,並把它們通過管道傳給錯誤處理器 ); }
// 返回結果 private extractData(res: Response) { return res as ResultData<any>; }
// 錯誤消息類 private handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.error('An error occurred:', error.error.message); } else { console.error( `Backend returned code ${error.status}, ` + `body was: ${error.error}` ); } return throwError('Something bad happened; please try again later.'); }
retry()
有時候,錯誤只是臨時性的,只要重試就可能會自動消失。RxJS 庫提供了幾個 retry 操作符,retry()可以對失敗的 Observable 自動重新訂閱幾次。對 HttpClient 方法調用的結果進行重新訂閱會導致重新發起 HTTP 請求。
public post(url: string, data = {}): Observable<any> { return this.http.post(url, data, httpOptions).pipe( map(this.extractData), retry(3),//重試失敗的請求,最多3次 catchError(this.handleError) ); }
添加請求頭
在向API發起請求很多需要添加額外的請求頭。 比如,它們可能需要一個 Content-Type 頭來顯式定義請求體的 MIME 類型。 也可能服務器會需要一個認證用的令牌(token)。
HeroesService 在 httpOptions 對象中就定義了一些這樣的請求頭,並把它傳給每個 HttpClient 的保存型方法。
public getJsonHeader(header?: HttpHeaders): HttpHeaders { if (header == null) { header = new HttpHeaders(); } return header.set('Content-Type', 'application/json')
.set('Authorization', 'Bearer ' + this.token); }
addUser (user: UserInfo): Observable<boolean> { return this.http.post<boolean>(url, user, this.getJsonHeader()) .pipe( catchError(this.handleError('addUser', user)) ); }
3.攔截請求
http 攔截機制是 @angular/common/http
中的主要特性之一。 可以聲明一些攔截器,用於監視和記錄從應用發送到服務器的 HTTP 請求。 攔截器還可以用監視和轉換從服務器返回到本應用的那些響應。實現攔截器,就要實現一個實現了 HttpInterceptor
接口中的 intercept()
方法的類。大多數攔截器攔截都會在傳入時檢查請求,然后把(可能被修改過的)請求轉發給 next
對象的 handle()
方法,而 next
對象實現了 HttpHandler
接口。
import { HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { Observable } from 'rxjs'; export interface HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>; }
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http'; import { finalize, tap } from 'rxjs/operators'; import { HttpInterceptor } from './HttpInterceptor'; import { LOGACTION } from '../types/model'; import { V5LogSQLService } from '../v5logSql.service'; @Injectable() export class NoopInterceptor implements HttpInterceptor { constructor(private log: V5LogSQLService) { } intercept(req: HttpRequest<any>, next: HttpHandler) { const startTime = Date.now(); let status: number; return next.handle(req).pipe( tap( event => { if (event instanceof HttpResponse) { status = event.status; } }, error => status = error.status ), finalize(() => { this.log.log(LOGACTION.HTTP, '' , JSON.stringify({ elapsedTime: (Date.now() - startTime) + 'ms', url: req.urlWithParams, status: status, param: req.body })); }) ); } }
next 對象表示攔截器鏈表中的下一個攔截器。 這個鏈表中的最后一個 next 對象就是 HttpClient 的后端處理器(backend handler),它會把請求發給服務器,並接收服務器的響應。大多數的攔截器都會調用 next.handle(),以便這個請求流能走到下一個攔截器,並最終傳給后端處理器。 攔截器也可以不調用 next.handle(),使這個鏈路短路,並返回一個帶有人工構造出來的服務器響應的 自己的 Observable,這是一種常見的中間件模式。
使用這個攔截器
這個 NoopInterceptor 就是一個由 Angular 依賴注入 (DI)系統管理的服務。 像其它服務一樣,你也必須先提供這個攔截器類,應用才能使用它。
由於攔截器是 HttpClient 服務的(可選)依賴,所以你必須在提供 HttpClient 的同一個(或其各級父注入器)注入器中提供這些攔截器。 那些在 DI 創建完 HttpClient 之后再提供的攔截器將會被忽略。由於在 AppModule 中導入了 HttpClientModule,導致本應用在其根注入器中提供了 HttpClient。所以你也同樣要在 AppModule 中提供這些攔截器。
注意 multi: true
選項。 這個必須的選項會告訴 Angular HTTP_INTERCEPTORS
是一個多重提供商的令牌,表示它會注入一個多值的數組,而不是單一的值。
在從 @angular/common/http 中導入了 HTTP_INTERCEPTORS 注入令牌之后,編寫如下的 NoopInterceptor 提供商注冊語句:
AppModule根模塊
{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
創建一個封裝桶(barrel)文件,用於把所有攔截器都收集起來,一起提供給 httpInterceptorProviders
數組。
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { NoopInterceptor } from './noop-interceptor'; export const httpInterceptorProviders = [ { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true }, ];
使用:
providers: [
httpInterceptorProviders
],
攔截請求還有,攔截的順序,以及httpEvent等。 HttpRequest
和 HttpResponse是只讀的,只可對其監視 or 日志記錄,要想修改該請求,就要先克隆它,並修改這個克隆體,然后再把這個克隆體傳給
next.handle(),
你可以用一步操作中完成對請求的克隆和修改。具體看 https://angular.cn/guide/http#intercepting-requests-and-responses,因為我對這個不熟現實開發中並未使用,僅僅使用攔截器記錄監聽請求記錄日志而已。
攔截器的使用場景有:設置默認請求頭,記日志,緩存,返回多值可觀察對象,監聽進度事件等。
4.其他
1.前面提到過默認情況下把響應體當做無類型的 JSON 對象進行返回,如果請求返回來的是文件流等,就要另外做處理。
例如
getTextFile(filename: string) { return this.http.get(filename, {responseType: 'text'}) .pipe( tap( data => this.log(filename, data), error => this.logError(filename, error) ) ); }
2.RxJS 是一個庫,用於把異步調用和基於回調的代碼組合成函數式(functional)的、響應式(reactive)的風格。 很多 Angular API,包括 HttpClient 都會生成和消費 RxJS 的 Observable。在整個httpService中我們用了RxJS庫提供的幾個操作符 catchError Observable retry 等,學習RxJS,有利於開發者在整個應用程序開發的開發,可以提高效率,以及代碼質量。
此隨筆乃本人學習工作記錄,如有疑問歡迎在下面評論,轉載請標明出處。
如果對您有幫助請動動鼠標右下方給我來個贊,您的支持是我最大的動力。