Angular 與 API 溝通時,可以把讀取、更新、新增、刪除資料集中放在 service 裡,再透過 `HttpClient` 回傳 `Observable` 給 component 使用。`HttpClient` 的請求不會在建立時立刻送出,而是在 `subscribe` 或 `async pipe` 訂閱後才真正執行,這是新手最容易漏掉的一點。
這篇沿用 Angular Tour of Heroes 的 `HeroService` 範例,整理 `GET`、`PUT`、`POST`、`DELETE`、錯誤處理與搜尋功能。閱讀前如果先理解 Reactive Programming 與 RxJS,會更容易看懂 `Observable`、`Subject`、`pipe` 與 `switchMap` 的角色。
Angular 如何用 HttpClient 取得 API 資料?
Angular `HttpClient.get()` 適合放在 service 裡,讓 component 不直接碰 API 位址。`HttpClient.get()` 會回傳 `Observable`,訂閱之後才送出 HTTP request。
先把 `src/app/hero.service.ts` 從讀取假資料,改成呼叫 API。這裡的 `heroesUrl` 指向 `api/heroes`,也就是 Tour of Heroes 範例中的英雄資料端點。
```ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from './message.service';
@Injectable()
export class HeroService {
private heroesUrl = 'api/heroes';
constructor(
private http: HttpClient,
private messageService: MessageService,
) {}
/ 舊寫法:從 mock data 取得資料
getHeroes(): Observable<Hero[]> {
this.messageService.add('HeroService: fetched heroes');
return of(HEROES);
}
*/
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl);
}
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url);
}
}
```
所有 `HttpClient` 方法都會回傳 `Observable`。一般 `Observable` 可以持續送出多次資料,但 `http.get()` 通常只會送出一次 API 回應,收到資料後就完成。`http.get()` 預設會把回應當成 JSON,並依照 TypeScript generic 提供開發時的型別提示。
Angular HttpClient 發生錯誤時要怎麼處理?
Angular `HttpClient` 錯誤處理通常會放在 RxJS `pipe()` 裡。用 `catchError()` 回傳預設值,可以避免 API 失敗時整個畫面直接中斷。
先導入需要的 RxJS operators:
```ts
import { catchError, map, tap } from 'rxjs/operators';
```
接著用 `pipe()` 擴展 `Observable`,並在裡面接上 `catchError()`:
```ts
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
catchError(this.handleError('getHeroes', [])),
);
}
```
錯誤處理函式可以寫成通用 helper。`operation` 用來標記是哪一個操作失敗,`result` 則是 API 失敗時要回傳的預設資料。
```ts
/
- 處理 HTTP 錯誤,讓應用程式可以繼續運作而不直接丟出 exception。
- @param operation - 失敗的操作,例如 getHeroes
- @param result - API 失敗時要回傳的 Observable 內容
*/
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
return of(result as T);
};
}
```
在這個例子中,`T` 代表類型參數。當 `getHeroes()` 呼叫 `handleError('getHeroes', [])` 時,`T` 會對應到 `Hero[]`,所以 API 失敗仍能回傳 component 預期的資料型態。
Angular 如何用 PUT 修改伺服器資料?
Angular 修改伺服器資料時常用 `HttpClient.put()`。`put()` 主要接收三個參數:API 網址、要更新的資料,以及 headers 等 request options。

在 `HeroService` 裡新增更新英雄資料的方法:
```ts
/ 更新伺服器上的資料 */
updateHero(hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
tap(_ => this.log(`updated hero id=${hero.id}`)),
catchError(this.handleError<any>('updateHero')),
);
}
```
`httpOptions` 常見用途是設定 headers。傳送 JSON 給 API 時,可以明確宣告 `Content-Type`:
```ts
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
};
```
component 呼叫 `updateHero()` 時,也要訂閱 `Observable`。以下範例是在儲存完成後回到上一頁:
```ts
save(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}
```
RxJS 裡常見的兩個角色是 `Observable` 和 `Subscription`。`Observable` 負責產生資料,但建立後不會馬上啟動;`Subscription` 則代表某個訂閱動作,訂閱後才開始執行資料流。
Angular 如何用 POST 新增伺服器資料?
Angular 新增資料時常用 `HttpClient.post()`。`post()` 會把新物件送到 API,伺服器通常會回傳新增後的完整資料,例如補上 `id` 的英雄物件。
在 `HeroService` 新增 `addHero()`:
```ts
addHero(hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
catchError(this.handleError<Hero>('addHero')),
);
}
```
component 使用 `addHero()` 時,可以先整理輸入值,再把 API 回傳的新英雄塞回列表:
```ts
add(name: string): void {
name = name.trim();
if (!name) {
return;
}
this.heroService.addHero({ name } as Hero)
.subscribe(hero => {
this.heroes.push(hero);
});
}
```
`{ name } as Hero` 代表建立一個只有 `name` 的 `Hero` 物件。這個物件送到 API 後,伺服器會負責建立完整資料,回傳後再更新畫面上的 `heroes` 陣列。
Angular 如何用 DELETE 刪除伺服器資料?
Angular 刪除資料時常用 `HttpClient.delete()`。即使刪除成功後不需要處理回傳值,也仍然要訂閱 `Observable`,否則 HTTP delete request 不會送出。
在 `HeroService` 新增 `deleteHero()`:
```ts
deleteHero(hero: Hero | number): Observable<Hero> {
const id = typeof hero === 'number' ? hero : hero.id;
const url = `${this.heroesUrl}/${id}`;
return this.http.delete<Hero>(url, httpOptions);
}
```
component 呼叫刪除時,可以先從畫面列表移除,再呼叫 API:
```ts
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
```
這段程式碼的重點是最後的 `subscribe()`。`HttpClient.delete()` 回傳的 `Observable` 需要被訂閱才會執行,所以不能因為「刪除完成後沒有下一步」就省略訂閱。
Angular 搜尋功能如何搭配 Subject、async pipe 與 switchMap?
Angular 搜尋功能可以用 `Subject` 收集鍵盤輸入,再用 `debounceTime()`、`distinctUntilChanged()` 與 `switchMap()` 控制 API 查詢節奏。`async pipe` 會自動訂閱畫面上的 `Observable`。
先在 `src/app/hero.service.ts` 增加搜尋方法。若搜尋字串是空白,就直接回傳空陣列,避免送出沒有意義的 API request。
```ts
searchHeroes(term: string): Observable<Hero[]> {
if (!term.trim()) {
return of([]);
}
return this.http.get<Hero[]>(`api/heroes/?name=${term}`).pipe(
tap(_ => this.log(`found heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', [])),
);
}
```
建立 `HeroSearchComponent`:
```bash
ng generate component hero-search
```
修改 `src/app/hero-search/hero-search.component.html`:
```html
Hero Search
```
`heroes$ | async` 這一段很關鍵。`heroes
接著修改 `src/app/hero-search/hero-search.component.ts`:
```ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { of } from 'rxjs/observable/of';
import {
debounceTime,
distinctUntilChanged,
switchMap,
} from 'rxjs/operators';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-search',
templateUrl: './hero-search.component.html',
styleUrls: ['./hero-search.component.css'],
})
export class HeroSearchComponent implements OnInit {
heroes$: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(private heroService: HeroService) {}
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroes$ = this.searchTerms.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
}
}
```
`Subject` 本身也是一種 `Observable`,可以用 `next(value)` 把新值推進資料流。這裡的 `search()` 與 `keyup` 事件綁定,所以每次使用者輸入文字時,`searchTerms` 都會收到新的搜尋字串。
```html
```
搜尋資料流的三個 operator 分工如下:
| Operator | 作用 | 為什麼搜尋功能需要 |
|---|---|---|
| `debounceTime(300)` | 每次輸入後等待 300 毫秒 | 避免每打一個字就立刻查 API |
| `distinctUntilChanged()` | 搜尋字串與上次相同就忽略 | 避免重複查同一個關鍵字 |
| `switchMap()` | 新查詢進來時只保留最新結果 | 避免較晚回來的舊 request 蓋掉新結果 |
`switchMap()` 可以處理多個 HTTP request 回傳順序不固定的問題。即使前一個 HTTP request 還在進行,`switchMap()` 也只會把最新搜尋字串對應的結果交給畫面使用;舊結果抵達應用程式前會被丟棄。
完成後,搜尋元件畫面會像下面這樣:

Angular HttpClient 新手最容易漏掉哪些細節?
Angular HttpClient 新手最容易漏掉三件事:沒有訂閱就不會送出請求、TypeScript generic 不等於 runtime 驗證,以及搜尋時要避免舊結果覆蓋新結果。
把這篇的重點整理成檢查表:
| 檢查項目 | 判斷方式 |
|---|---|
| 是否真的送出 request | 確認有 `subscribe()`、`async pipe`,或其他訂閱方式 |
| 回傳型別是否符合畫面需要 | `http.get<Hero[]>()` 只能提供編譯期提示,API 邊界仍要小心資料格式 |
| API 失敗時畫面是否能繼續運作 | 用 `catchError()` 回傳預設值,避免 component 收到非預期資料 |
| 修改資料是否有設定 headers | 傳送 JSON 時設定 `Content-Type: application/json` |
| 搜尋是否造成過多 request | 用 `debounceTime()` 與 `distinctUntilChanged()` 控制查詢頻率 |
| 搜尋結果是否可能被舊 request 覆蓋 | 用 `switchMap()` 只保留最新搜尋結果 |
實作 CRUD 時,我會先把 API 呼叫都集中在 service,再讓 component 只處理畫面事件與狀態更新。這樣後續要加錯誤訊息、loading 狀態、token header 或測試替身,都不需要到每個 component 裡翻找 API 邏輯。
常見問題
Angular HttpClient 為什麼一定要 subscribe?
Angular `HttpClient` 回傳的 `Observable` 是 lazy 的資料流。沒有 `subscribe()`、`async pipe` 或其他訂閱方式時,HTTP request 不會真正送到伺服器。
Angular HttpClient GET 會回傳幾次資料?
Angular `HttpClient.get()` 通常只會回傳一次 API response,接著就完成。一般 `Observable` 可以持續送出多次資料,但 HTTP request 的生命週期通常是「送出一次、收到一次、結束」。
Angular HttpClient 的 generic 會檢查 JSON 格式嗎?
Angular `http.get<Hero[]>()` 的 generic 只提供 TypeScript 編譯期型別提示,不會在 runtime 檢查 API 回傳是否真的符合 `Hero[]`。重要 API 建議在資料進入應用程式邊界時再做驗證或防呆。
Angular 搜尋功能為什麼常用 async pipe?
Angular `async pipe` 可以在 template 自動訂閱 `Observable`,資料更新時刷新畫面,component 銷毀時也會取消訂閱。搜尋結果這種畫面資料流很適合交給 `async pipe` 處理。
Angular 搜尋功能為什麼要用 switchMap?
Angular 搜尋功能會隨著鍵盤輸入送出多次 request,舊 request 可能比新 request 晚回來。`switchMap()` 會只保留最新搜尋字串對應的結果,避免畫面被過期資料覆蓋。
延伸閱讀
- Angular HttpClient 教學:GET、POST、Header、Params 與錯誤處理
- Angular Service 依賴注入教學:providers、InjectionToken 與分層注入
- Angular Router 基礎教學:Routes、RouterLink、RouterOutlet 與路由事件
- Angular Template Binding 教學:Interpolation、Property、Event 與 Two-way Binding
參考資料
- TechBridge:Reactive Programming 簡介與教學(以 RxJS 為例)
- ReactiveX:ReactiveX 官方網站
- iT 邦幫忙:30 天精通 RxJS
- Angular:Tour of Heroes Part 6 live example
- Angular:Tour of Heroes Part 6 download example
最後更新
本文最後更新於 2026-08-28。
關於作者 {#author}
Claire Chang | 企業 AI 導入與流程轉型顧問。專注於 AI Agent 架構設計、ERP 系統整合與企業 AI 治理。
首次發布:2017-12-26
