Angular HttpClient 與 API 請求流程示意圖

← INSIGHTS & PERSPECTIVES | 前端開發

Angular HTTP API 溝通教學:HttpClient、Observable 與 CRUD 範例

以 Angular Tour of Heroes 範例說明 HttpClient 如何取得、新增、修改、刪除 API 資料,並整理 Observable、subscribe、async pipe 與搜尋功能的用法。

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。

Angular HttpClient put 方法參數截圖

在 `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 Angular HTTP API 溝通教學:HttpClient、Observable 與 CRUD 範例|Claire Chang 是 `Observable<Hero[]>`,不能直接當一般陣列使用;`async pipe` 會替 template 自動訂閱資料流,資料更新時也會更新畫面。

接著修改 `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 Tour of Heroes 搜尋結果範例

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 邏輯。

常見問題

QAngular HttpClient 為什麼一定要 subscribe?

Angular `HttpClient` 回傳的 `Observable` 是 lazy 的資料流。沒有 `subscribe()`、`async pipe` 或其他訂閱方式時,HTTP request 不會真正送到伺服器。

QAngular HttpClient GET 會回傳幾次資料?

Angular `HttpClient.get()` 通常只會回傳一次 API response,接著就完成。一般 `Observable` 可以持續送出多次資料,但 HTTP request 的生命週期通常是「送出一次、收到一次、結束」。

QAngular HttpClient 的 generic 會檢查 JSON 格式嗎?

Angular `http.get<Hero[]>()` 的 generic 只提供 TypeScript 編譯期型別提示,不會在 runtime 檢查 API 回傳是否真的符合 `Hero[]`。重要 API 建議在資料進入應用程式邊界時再做驗證或防呆。

QAngular 搜尋功能為什麼常用 async pipe?

Angular `async pipe` 可以在 template 自動訂閱 `Observable`,資料更新時刷新畫面,component 銷毀時也會取消訂閱。搜尋結果這種畫面資料流很適合交給 `async pipe` 處理。

QAngular 搜尋功能為什麼要用 switchMap?

Angular 搜尋功能會隨著鍵盤輸入送出多次 request,舊 request 可能比新 request 晚回來。`switchMap()` 會只保留最新搜尋字串對應的結果,避免畫面被過期資料覆蓋。

延伸閱讀

參考資料

最後更新

本文最後更新於 2026-08-28。

關於作者 {#author}

Claire Chang | 企業 AI 導入與流程轉型顧問。專注於 AI Agent 架構設計、ERP 系統整合與企業 AI 治理。

首次發布:2017-12-26