HttpClient

基本概念

在建構需要與儲存在文件或資料庫中的數據進行交互的 Angular 應用時,重要的一點是,你不會直接與資料庫對話(You don't directly talk to a database),而是透過 API 發送 HTTP 請求,並接收回應。

Angular 提供了 HttpClient 服務來處理這些 HTTP 請求,它是 Angular 應用中與後端服務通訊的標準方式。

設置 HttpClient

步驟 1: 注入 HttpClient

在組件或服務中注入 HttpClient:

import { HttpClient } from '@angular/common/http';

// 使用 inject 函數 (Angular 14+)
private httpClient = inject(HttpClient);

// 或使用建構函數注入
constructor(private httpClient: HttpClient) {}

步驟 2: 提供 HttpClient 服務

如果整個應用程式都需要使用 HttpClient,通常在 main.ts 中全域提供:

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideHttpClient } from '@angular/common/http';

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient()],
}).catch((err) => console.error(err));

對於使用 NgModule 的傳統應用,在 AppModule 中引入 HttpClientModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  bootstrap: [AppComponent]
})
export class AppModule {}

發送 HTTP 請求

HttpClient 提供了多種方法來發送不同類型的 HTTP 請求。所有這些方法都返回 Observable,這意味著它們是非同步的。

GET 請求

用於從伺服器獲取資料:

// 基本 GET 請求
this.httpClient.get('https://api.example.com/items').subscribe({
  next: (data) => console.log('成功獲取數據:', data),
  error: (error) => console.error('發生錯誤:', error)
});

// 使用類型參數的 GET 請求
interface Item {
  id: number;
  name: string;
}

this.httpClient.get<Item[]>('https://api.example.com/items').subscribe({
  next: (items) => {
    // items 已經是 Item[] 類型
    console.log(`獲取了 ${items.length} 個項目`);
  }
});

POST 請求

用於向伺服器發送數據:

const newItem = { name: '新項目', value: 42 };

this.httpClient.post('https://api.example.com/items', newItem).subscribe({
  next: (response) => console.log('項目已創建:', response),
  error: (error) => console.error('創建失敗:', error)
});

PUT 請求

用於更新伺服器上的資源:

const updatedItem = { id: 1, name: '更新的項目', value: 99 };

this.httpClient.put(`https://api.example.com/items/${updatedItem.id}`, updatedItem).subscribe({
  next: (response) => console.log('項目已更新:', response),
  error: (error) => console.error('更新失敗:', error)
});

DELETE 請求

用於刪除伺服器上的資源:

const itemId = 1;

this.httpClient.delete(`https://api.example.com/items/${itemId}`).subscribe({
  next: () => console.log('項目已刪除'),
  error: (error) => console.error('刪除失敗:', error)
});

處理請求選項

HttpClient 方法接受一個選項物件作為第二個參數,可以用來設定各種請求配置:

this.httpClient.get('https://api.example.com/items', {
  headers: {
    'Authorization': 'Bearer token123',
    'Content-Type': 'application/json'
  },
  params: {
    'sort': 'asc',
    'page': '1'
  },
  observe: 'response', // 獲取完整 HTTP 回應,包括標頭
  responseType: 'json' // 預設值
}).subscribe({
  next: (response) => console.log('完整回應:', response),
  error: (error) => console.error('請求失敗:', error)
});

錯誤處理

使用 RxJS 的 catchError 運算符可以更優雅地處理 HTTP 請求錯誤:

import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

this.httpClient.get('https://api.example.com/items')
  .pipe(
    catchError(error => {
      console.error('發生錯誤:', error);
      // 可以在這裡進行錯誤處理,例如顯示通知或重新導向
      return throwError(() => new Error('請求失敗'));
    })
  )
  .subscribe({
    next: (data) => console.log('數據:', data)
  });

HTTP 攔截器 (Interceptors)

HTTP 攔截器可以用來統一處理所有請求和回應,例如添加認證標頭、記錄請求或處理錯誤:

// auth.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 獲取 token(從本地儲存或服務)
    const token = localStorage.getItem('token');
    
    // 如果有 token,添加到請求標頭
    if (token) {
      const authReq = request.clone({
        headers: request.headers.set('Authorization', `Bearer ${token}`)
      });
      return next.handle(authReq);
    }
    
    return next.handle(request);
  }
}

然後在 providers 中註冊攔截器:

import { provideHttpClient, withInterceptors } from '@angular/common/http';

// 在 main.ts 中
bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor])
    )
  ],
});

最佳實踐

  1. 創建專用的服務:將 HTTP 請求邏輯封裝在專用的服務中,而不是直接在組件中使用 HttpClient

  2. 使用類型:總是為 HTTP 請求和回應指定類型,以獲得更好的類型安全性

  3. 取消請求:使用 RxJS 的 takeUntil 運算符來取消不再需要的請求,避免記憶體洩漏

  4. 將 API URL 放在環境配置:不要在程式碼中硬編碼 API URL,而是使用環境配置

  5. 優雅地處理錯誤:實現全域錯誤處理機制,提供一致的用戶體驗

// data.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, catchError, throwError } from 'rxjs';
import { environment } from '../environments/environment';

interface Item {
  id: number;
  name: string;
}

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private httpClient = inject(HttpClient);
  private apiUrl = environment.apiUrl + '/items';

  getItems(): Observable<Item[]> {
    return this.httpClient.get<Item[]>(this.apiUrl)
      .pipe(
        catchError(this.handleError)
      );
  }

  getItem(id: number): Observable<Item> {
    return this.httpClient.get<Item>(`${this.apiUrl}/${id}`)
      .pipe(
        catchError(this.handleError)
      );
  }

  createItem(item: Omit<Item, 'id'>): Observable<Item> {
    return this.httpClient.post<Item>(this.apiUrl, item)
      .pipe(
        catchError(this.handleError)
      );
  }

  private handleError(error: any) {
    console.error('API 錯誤:', error);
    return throwError(() => new Error('請求失敗,請稍後再試'));
  }
}

Last updated