Angular學習筆記11:HTTP(1)
HTTP
繼學習筆記10後,可以在路由器中配置相關路由使使用者可在不同的檢視切換。
在一個實際的專案中,資料使來自遠端的伺服器上的,在Angular中,Angular通過HttpClient與遠端伺服器進行通訊。
模擬資料伺服器
使用 記憶體 Web API(In-memory Web API) 模擬出的遠端資料伺服器通訊(在實際專案中不使用這個模組,在這裡只是模擬後端資料)。
a. 安裝in-memory-web-api
bogon:demo wjy$ npm install angular-in-memory-web-api --save
+ [email protected]
added 1 package in 51.529s
b.在app.module.ts 匯入HttpClientInMemoryWebApiModule,HttpClient
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import {HttpClientModule} from '@angular/common/http';
c.把 HttpClientInMemoryWebApiModule
新增到 @NgModule.imports
陣列中(放在 HttpClient
之後), 然後使用 InMemoryDataService
來配置它。
HttpClientModule,
HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {dataEncapsulation: false}),
forRoot()
配置方法接受一個 InMemoryDataService
類(初期的記憶體資料庫)作為引數。
d.建立一個數據服務:in-memory-data
bogon:demo wjy$ ng generate service InMemoryData CREATE src/app/in-memory-data.service.spec.ts (412 bytes) CREATE src/app/in-memory-data.service.ts (141 bytes)
在AppModule中引入InMemoryDataService
import {InMemoryDataService} from './in-memory-data.service';
這個檔案代替了之前的 mock-heroes.ts,此時這個時候就可以安全的刪除mock-heroes.ts。
當在後期可以從伺服器獲取資料的時候,就可以刪除現在這個檔案了。
Hero與HTTP
a.匯入一些需要的HTTP符號:
import { HttpClient, HttpHeaders } from '@angular/common/http';
b.把 HttpClient
注入到建構函式中一個名叫 http
的私有屬性中。
constructor(
private http: HttpClient,
private messageService: MessageService) { }
c.保留對 MessageService
的注入。你將會頻繁呼叫它,因此請把它包裹進一個私有的 log
方法中。
private log(message: string) {
this.messageService.add(`HeroService: ${message}`);
}
d.把伺服器上英雄資料資源的訪問地址定義為 heroesURL
。
private heroesUrl = 'api/heroes';
通過 HttpClient 獲取英雄
修改當前的 HeroService.getHeroes()
使用 HttpClient 來模擬英雄資料返回為 Observable<Hero[]>
格式。
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
}
儲存,重新整理瀏覽器,應用就會模擬伺服器被成功讀取。
但是在這裡並沒有修改檢視hero詳情的函式,但是在此刻應用並沒有報錯。這裡是因為: of / HttpClient 返回的都是Observable<Hero[]>
。
Http 方法返回單個值
所有的 HttpClient
方法都會返回某個值的 RxJS Observable
。
HTTP 是一個請求/響應式協議。你發起請求,它返回單個的響應。
通常,Observable
可以在一段時間內返回多個值。 但來自 HttpClient
的 Observable
總是發出一個值,然後結束,再也不會發出其它值。
具體到這次 HttpClient.get
呼叫,它返回一個 Observable<Hero[]>
,在這裡“返回的這個Hero陣列是可觀察物件”。在實踐中,它也只會返回一個英雄陣列。
HttpClient.get
返回響應資料
HttpClient.get
預設情況下把響應體當做無型別的 JSON 物件進行返回。 如果指定了可選的模板型別 <Hero[]>
,就會給返回你一個型別化的物件。JSON 資料的具體形態是由伺服器的資料 API 決定的。 英雄指南的資料 API 會把英雄資料作為一個數組進行返回。
錯誤處理
有時候在從後端獲取資料,可能會因為一些其他原因,發生一些錯誤。此刻,在HttpClient.get()方法中應該捕獲錯誤,並做適當的處理,如果要捕獲錯誤,就需要
使用 RxJS 的 catchError()
操作符來建立對 Observable 結果的處理管道(pipe)。
從 rxjs/operators
中匯入 catchError
符號。
import { catchError} from 'rxjs/operators';
使用 .pipe()
方法來擴充套件 Observable
的結果,並給它一個 catchError()
操作符
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
catchError(this.handleError('getHeroes', []))
);
}
此時在this.handleError('getHeroes',[]),這裡會報錯,是因為,暫時還沒有宣告這個方法。
catchError()
操作符會攔截失敗的 Observable
。 它把錯誤物件傳給錯誤處理器,錯誤處理器會處理這個錯誤。
handleError
在實際的專案中,很多component都需要從遠端伺服器獲取資料,這過程中可能會需要很多的請求,而這麼多的請求中都有可能會發生錯誤,在發生錯誤的時候,就需要將用類似的 handleError()這種方法,如此之來,handleError() 將會在很多 HeroService
的方法之間共享,所以要把它通用化,以支援這些彼此不同的需求。它不再直接處理這些錯誤,而是返回給 catchError
返回一個錯誤處理函式。還要用操作名和出錯時要返回的安全值來對這個錯誤處理函式進行配置。
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error); // log to console instead
this.log(`${operation} failed: ${error.message}`);
return of(result as T);
};
}
在這個handleError中,處理錯誤的方法是:在控制檯中彙報了這個錯誤之後,這個處理器會彙報一個使用者友好的訊息,並給應用返回一個安全值,使它可以繼續工作。因為每個服務方法都會返回不同型別的 Observable
結果,因此 handleError()
也需要一個型別引數,以便它返回一個此型別的安全值,正如應用所期望的那樣。
窺探 Observable
HeroService
的方法將會窺探 Observable
的資料流,並通過 log()
函式往頁面底部發送一條訊息。
它們可以使用 RxJS 的 tap
操作符來實現,該操作符會檢視 Observable 中的值,使用那些值做一些事情,並且把它們傳出來。 這種 tap
回撥不會改變這些值本身。
a.在HeroService中匯入:tap
import {catchError, tap} from 'rxjs/operators';
b.修改getHeroes();
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(heroes => {
console.log(heroes);
this.log('fetched heroes');
}),
catchError(this.handleError('getHeroes', []))
);
}
在這裡可以打印出來獲取到的heroes;在控制檯可以看到如下資訊:
通過ID去獲取hero資訊
在實際專案中,有時候會遇到一個某種一系列類似事物的列表,然後要檢視這個一系列中的某一個的詳細資訊,此時可能就會需要類似於通過 api/hero/:id
的形式(比如 api/hero/:id
)獲取這個單個物件的全部資訊,修改 HeroService.getHero()
方法來發起請求
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url).pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
這裡的getHeroes()和修改前的
相比有三個顯著的差異。
-
它使用想獲取的英雄的 id 構建了一個請求 URL。
-
伺服器應該使用單個英雄作為迴應,而不是一個英雄陣列。
-
所以,
getHero
會返回Observable<Hero>
(“一個可觀察的單個hero物件”),而不是一個可觀察的hero物件陣列。