HTTP Interceptor
什麼是 HTTP Interceptor?
HTTP Interceptor 是 Angular 提供的一種機制,可以攔截和修改 HTTP 請求和響應。它們像是中間件,允許我們在 HTTP 請求離開應用程式之前和響應到達應用程式之前執行程式碼。
常見用途
添加認證令牌到每個請求
處理和統一錯誤響應
記錄 HTTP 請求和響應
轉換請求和響應的格式
添加加載指示器
實現重試機制
基本實現
在 Angular 現代版本中,HTTP 攔截器通常是作為函數實現的,特別是在 Angular 14+ 的獨立元件中。
請求攔截器
下面是一個基本的請求攔截器,它會在每個請求中添加自定義標頭:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import {
HttpHandlerFn,
HttpRequest,
provideHttpClient,
withInterceptors,
} from '@angular/common/http';
function loggingInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn) {
// 克隆請求並添加自定義標頭
const modifiedRequest = request.clone({
headers: request.headers.set('X-DEBUG', 'TESTING'),
});
console.log('[Outgoing Request]');
console.log(request);
// 將修改後的請求傳遞給下一個處理程序
return next(modifiedRequest);
}
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withInterceptors([loggingInterceptor]))],
}).catch((err) => console.error(err));
當你使用 HttpClient 發送請求時,這個攔截器會自動為每個請求添加 X-DEBUG: TESTING
標頭。

響應攔截器
攔截器也可以處理從服務器返回的響應:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import {
HttpEventType,
HttpHandlerFn,
HttpRequest,
provideHttpClient,
withInterceptors,
} from '@angular/common/http';
import { tap } from 'rxjs';
function loggingInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn) {
console.log('[Outgoing Request]');
console.log(request);
// 處理請求後的響應
return next(request).pipe(
tap({
next: (event) => {
if (event.type === HttpEventType.Response) {
console.log('[Incoming Response]');
console.log(event.status);
console.log(event.body);
}
},
})
);
}
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withInterceptors([loggingInterceptor]))],
}).catch((err) => console.error(err));
這個攔截器會記錄所有傳出請求和傳入響應的詳細信息。

同時處理請求和響應
一個完整的攔截器示例,同時處理請求和響應:
import { HttpEventType, HttpHandlerFn, HttpRequest } from '@angular/common/http';
import { tap } from 'rxjs';
export function authInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn) {
// 從本地存儲獲取令牌
const token = localStorage.getItem('auth_token');
// 如果有令牌,添加到請求標頭
let modifiedRequest = request;
if (token) {
modifiedRequest = request.clone({
headers: request.headers.set('Authorization', `Bearer ${token}`)
});
}
// 記錄請求
console.log(`[Request] ${request.method} ${request.url}`);
// 發送修改後的請求並處理響應
return next(modifiedRequest).pipe(
tap({
next: (event) => {
// 只處理完整的響應
if (event.type === HttpEventType.Response) {
console.log(`[Response] ${event.status} for ${request.url}`);
// 處理令牌過期情況
if (event.status === 401) {
console.log('Token expired. Redirecting to login...');
// 這裡可以添加重定向到登錄頁面的邏輯
}
}
},
error: (error) => {
console.error(`[Error] ${request.url}:`, error);
}
})
);
}
錯誤處理攔截器
專門用於統一處理錯誤的攔截器:
import { HttpHandlerFn, HttpRequest } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
export function errorInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn) {
return next(request).pipe(
catchError(error => {
// 處理不同類型的錯誤
if (error.status === 404) {
console.error('Resource not found');
// 可以顯示通知或導航到 404 頁面
} else if (error.status === 500) {
console.error('Server error');
// 可以顯示服務器錯誤的通知
} else if (!navigator.onLine) {
console.error('No internet connection');
// 可以顯示離線提示
}
// 將自定義錯誤消息添加到錯誤對象
const customError = {
message: error.error?.message || 'An unknown error occurred',
status: error.status,
url: request.url
};
// 將錯誤傳播給組件
return throwError(() => customError);
})
);
}
載入指示器攔截器
用於顯示全局載入指示器的攔截器:
import { HttpEventType, HttpHandlerFn, HttpRequest } from '@angular/common/http';
import { finalize, tap } from 'rxjs';
import { inject } from '@angular/core';
import { LoadingService } from './loading.service';
export function loadingInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn) {
const loadingService = inject(LoadingService);
// 增加活躍請求計數
loadingService.increaseRequestCount();
return next(request).pipe(
tap({
next: (event) => {
// 當收到完整響應時減少請求計數
if (event.type === HttpEventType.Response) {
loadingService.decreaseRequestCount();
}
}
}),
// 確保無論成功還是失敗都會減少請求計數
finalize(() => {
loadingService.decreaseRequestCount();
})
);
}
搭配的 LoadingService 可能如下所示:
import { Injectable, signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoadingService {
private requestCount = signal(0);
isLoading = this.requestCount.asReadonly();
increaseRequestCount() {
this.requestCount.update(count => count + 1);
}
decreaseRequestCount() {
this.requestCount.update(count => Math.max(0, count - 1));
}
}
註冊多個攔截器
攔截器按照註冊的順序執行,因此順序很重要:
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([
loadingInterceptor, // 首先啟動載入指示器
authInterceptor, // 然後添加認證
loggingInterceptor, // 接著記錄
errorInterceptor // 最後處理錯誤
])
)
],
}).catch((err) => console.error(err));
最佳實踐
保持攔截器輕量:攔截器會處理每個 HTTP 請求,所以應避免在其中執行複雜操作。
處理所有可能的情況:確保攔截器能夠處理所有可能的請求和響應場景。
正確處理流:使用 RxJS 運算符如
tap
、catchError
和finalize
來確保流正確完成。克隆請求:始終使用
request.clone()
來修改請求,因為 HttpRequest 對象是不可變的。考慮副作用:避免在攔截器中產生不必要的副作用,特別是那些可能影響應用狀態的操作。
Last updated