前端應用都須要經過 HTTP 協議與後端服務器通信,@angular/common/http
中的 HttpClient
類爲 Angular 應用程序提供的 API 來實現 HTTP 客戶端功能。它基於瀏覽器提供的 XMLHttpRequest
接口。 HttpClient
帶來的其它優勢包括:可測試性、強類型的請求和響應對象、發起請求與接收響應時的攔截器支持,以及更好的、基於可觀察(Observable)對象的 API 以及流式錯誤處理機制。前端
在根模塊AppModule
導入HttpClientModule。由於大多數應用中,多處多都要使用到http服務,因此直接導入到根模塊去。
react
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服務了json
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.封裝httpServicebootstrap
一般一個應用咱們不會直接使用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,有利於開發者在整個應用程序開發的開發,能夠提升效率,以及代碼質量。
此隨筆乃本人學習工做記錄,若有疑問歡迎在下面評論,轉載請標明出處。
若是對您有幫助請動動鼠標右下方給我來個贊,您的支持是我最大的動力。