Angular HttpClient và Interceptor năm 2026: Xử lý Request và Câu hỏi Phỏng vấn
Tìm hiểu cách sử dụng Angular HttpClient và functional interceptor để xử lý HTTP request, xác thực token, retry logic và caching. Bao gồm các câu hỏi phỏng vấn Angular thường gặp.

Angular HttpClient interceptor thay đổi cách ứng dụng xử lý HTTP request, từ việc thêm header xác thực đến triển khai retry logic. Angular 20 đã chuẩn hóa functional interceptor như phương pháp được khuyến nghị, thay thế pattern dựa trên class bằng API dễ dự đoán hơn và có khả năng tree-shakable tốt hơn.
Angular 20 khuyến nghị sử dụng functional interceptor với withInterceptors() thay vì class-based interceptor. Functional interceptor có thứ tự thực thi dễ dự đoán hơn và tích hợp tốt hơn với standalone component.
Thiết lập HttpClient với provideHttpClient
Trước khi viết interceptor, HttpClient cần được cấu hình trong bootstrap ứng dụng. Kiến trúc standalone của Angular sử dụng provideHttpClient() thay vì HttpClientModule cũ.
Việc thiết lập được thực hiện trong cấu hình ứng dụng:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { authInterceptor } from './interceptors/auth.interceptor';
import { loggingInterceptor } from './interceptors/logging.interceptor';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
// Interceptor được thực thi theo thứ tự mảng
withInterceptors([authInterceptor, loggingInterceptor])
),
],
});Interceptor được thực thi theo thứ tự được chỉ định trong mảng. Interceptor đầu tiên xử lý outgoing request trước, và interceptor cuối cùng nhìn thấy response trước.
Cấu trúc Functional Interceptor
Functional interceptor nhận request và hàm next. Gọi next(req) chuyển request đến interceptor tiếp theo trong chuỗi hoặc đến backend nếu đây là interceptor cuối cùng.
Cấu trúc cơ bản như sau:
import { HttpInterceptorFn, HttpRequest, HttpHandlerFn, HttpEvent } from '@angular/common/http';
import { Observable, tap } from 'rxjs';
export const loggingInterceptor: HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
const startTime = performance.now();
console.log(`[HTTP] ${req.method} ${req.url}`);
return next(req).pipe(
tap({
next: () => {
const duration = Math.round(performance.now() - startTime);
console.log(`[HTTP] ${req.method} ${req.url} completed in ${duration}ms`);
},
error: (err) => {
console.error(`[HTTP] ${req.method} ${req.url} failed:`, err.message);
},
})
);
};Interceptor này ghi lại method và URL của request, sau đó đo thời gian response. Operator tap quan sát response mà không sửa đổi nó.
Authentication Interceptor với Token Injection
Hầu hết các ứng dụng cần đính kèm authentication token vào outgoing request. Interceptor clone request với header Authorization, vì đối tượng HttpRequest là immutable.
Pattern này lấy token từ service và đính kèm nó:
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
// Inject service sử dụng hàm inject() của Angular
const authService = inject(AuthService);
const token = authService.getAccessToken();
// Bỏ qua auth header cho public endpoint
if (req.url.includes('/public/') || !token) {
return next(req);
}
// Clone request với header Authorization
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
return next(authReq);
};Hàm inject() hoạt động vì interceptor được thực thi trong injection context của Angular. Điều này loại bỏ boilerplate constructor injection từ class-based interceptor.
Xử lý Error và Retry Logic
Lỗi mạng có thể xảy ra bất cứ lúc nào. Retry interceptor có thể tự động thử lại các request thất bại với exponential backoff, cải thiện độ tin cậy mà không cần thay đổi mọi HTTP call.
Các operator RxJS xử lý retry logic:
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { retry, timer } from 'rxjs';
export const retryInterceptor: HttpInterceptorFn = (req, next) => {
// Chỉ retry GET request (idempotent)
if (req.method !== 'GET') {
return next(req);
}
return next(req).pipe(
retry({
count: 3,
delay: (error: HttpErrorResponse, retryCount: number) => {
// Không retry client error (4xx)
if (error.status >= 400 && error.status < 500) {
throw error;
}
// Exponential backoff: 1s, 2s, 4s
const delayMs = Math.pow(2, retryCount - 1) * 1000;
console.log(`Retry #${retryCount} in ${delayMs}ms`);
return timer(delayMs);
},
})
);
};Interceptor này chỉ thử lại GET request vì POST, PUT và DELETE không phải là idempotent. Client error (4xx) bỏ qua retry vì server đã từ chối request một cách rõ ràng.
Sẵn sàng chinh phục phỏng vấn Angular?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Response Caching với HttpContext
HttpClient cung cấp HttpContext để truyền metadata giữa interceptor và code gọi. Caching interceptor có thể đọc context token để xác định hành vi cache.
Cache token và interceptor hoạt động cùng nhau:
import { HttpInterceptorFn, HttpContextToken, HttpResponse } from '@angular/common/http';
import { of, tap } from 'rxjs';
// Context token để kiểm soát caching theo từng request
export const CACHE_REQUEST = new HttpContextToken<boolean>(() => false);
const cache = new Map<string, HttpResponse<unknown>>();
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
// Chỉ cache nếu được yêu cầu rõ ràng qua context
if (!req.context.get(CACHE_REQUEST) || req.method !== 'GET') {
return next(req);
}
const cacheKey = req.urlWithParams;
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
console.log(`[Cache] HIT: ${cacheKey}`);
return of(cachedResponse.clone());
}
return next(req).pipe(
tap((event) => {
if (event instanceof HttpResponse) {
console.log(`[Cache] STORE: ${cacheKey}`);
cache.set(cacheKey, event.clone());
}
})
);
};Code gọi chọn bật caching với context token:
import { HttpClient, HttpContext } from '@angular/common/http';
import { CACHE_REQUEST } from '../interceptors/cache.interceptor';
export class UserService {
constructor(private http: HttpClient) {}
getUser(id: string) {
return this.http.get(`/api/users/${id}`, {
context: new HttpContext().set(CACHE_REQUEST, true),
});
}
}Pattern này làm cho hành vi caching rõ ràng tại call site thay vì ẩn trong cấu hình global.
Token Refresh với Xử lý 401
Khi access token hết hạn, server trả về response 401. Interceptor có thể bắt điều này, refresh token, và thử lại request gốc một cách trong suốt.
Logic refresh phối hợp nhiều concurrent request:
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, switchMap, throwError, BehaviorSubject, filter, take } from 'rxjs';
import { AuthService } from '../services/auth.service';
let isRefreshing = false;
const refreshTokenSubject = new BehaviorSubject<string | null>(null);
export const tokenRefreshInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status !== 401 || req.url.includes('/auth/refresh')) {
return throwError(() => error);
}
if (isRefreshing) {
// Đợi refresh hoàn thành
return refreshTokenSubject.pipe(
filter((token) => token !== null),
take(1),
switchMap((token) => {
const retryReq = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
return next(retryReq);
})
);
}
isRefreshing = true;
refreshTokenSubject.next(null);
return authService.refreshToken().pipe(
switchMap((response) => {
isRefreshing = false;
refreshTokenSubject.next(response.accessToken);
const retryReq = req.clone({
setHeaders: { Authorization: `Bearer ${response.accessToken}` },
});
return next(retryReq);
}),
catchError((refreshError) => {
isRefreshing = false;
authService.logout();
return throwError(() => refreshError);
})
);
})
);
};BehaviorSubject ngăn chặn nhiều refresh call khi nhiều request thất bại đồng thời. Sau khi token được refresh, tất cả request đang chờ thử lại với token mới.
Câu hỏi Phỏng vấn: HttpClient và Interceptor
Các cuộc phỏng vấn kỹ thuật thường kiểm tra hiểu biết về các pattern xử lý HTTP. Những câu hỏi này xuất hiện thường xuyên trong các vị trí Angular.
Q: Tại sao functional interceptor được ưu tiên hơn class-based interceptor?
Functional interceptor có thứ tự thực thi dễ dự đoán hơn. Tài liệu Angular nêu rõ rằng functional interceptor với withInterceptors() duy trì thứ tự mảng nghiêm ngặt, trong khi DI-based interceptor có thể gặp vấn đề về thứ tự trong các phân cấp module phức tạp. Functional interceptor cũng tạo ra bundle nhỏ hơn vì tree-shake tốt hơn.
Q: Làm thế nào để ngăn interceptor chạy trên các request cụ thể?
Có hai cách tiếp cận. Thứ nhất, kiểm tra pattern URL trong interceptor và gọi next(req) mà không sửa đổi cho các path được loại trừ. Thứ hai, sử dụng token HttpContext mà code gọi thiết lập để opt-out khỏi hành vi interceptor. Cách tiếp cận context giữ quyết định loại trừ tại call site.
Q: Điều gì xảy ra nếu quên gọi next() trong interceptor?
Request không bao giờ đến server. Observable được trả về bởi next() đại diện cho phần còn lại của interceptor chain và HTTP call thực tế. Nếu không gọi nó, các interceptor downstream và backend không bao giờ nhìn thấy request. Điều này có chủ đích cho caching interceptor trả về cached response.
Q: Làm thế nào để xử lý concurrent request trong quá trình token refresh?
Sử dụng BehaviorSubject để phối hợp. Khi 401 đầu tiên kích hoạt refresh, đặt flag và emit null trên subject. Các 401 tiếp theo chờ trên subject với filter cho đến khi token mới đến. Điều này ngăn chặn nhiều refresh call và đảm bảo tất cả pending request thử lại với cùng token mới.
Q: Interceptor có thể sửa đổi response body không?
Có. Sử dụng map() trên Observable được trả về bởi next() để transform đối tượng HttpResponse. Vì response là immutable, clone chúng với body đã sửa đổi. Pattern này hoạt động để thêm computed field, unwrap API envelope, hoặc normalize cấu trúc dữ liệu.
Để chuẩn bị phỏng vấn Angular thêm, xem module câu hỏi phỏng vấn HttpClient.
Testing Functional Interceptor
Interceptor yêu cầu testing với mocked HTTP handler. HttpTestingController của Angular hoạt động với interceptor được đăng ký qua provideHttpClient().
Thiết lập test như sau:
import { TestBed } from '@angular/core/testing';
import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { authInterceptor } from './auth.interceptor';
import { AuthService } from '../services/auth.service';
describe('authInterceptor', () => {
let httpClient: HttpClient;
let httpTesting: HttpTestingController;
let authServiceSpy: jasmine.SpyObj<AuthService>;
beforeEach(() => {
authServiceSpy = jasmine.createSpyObj('AuthService', ['getAccessToken']);
TestBed.configureTestingModule({
providers: [
provideHttpClient(withInterceptors([authInterceptor])),
provideHttpClientTesting(),
{ provide: AuthService, useValue: authServiceSpy },
],
});
httpClient = TestBed.inject(HttpClient);
httpTesting = TestBed.inject(HttpTestingController);
});
it('should add Authorization header when token exists', () => {
authServiceSpy.getAccessToken.and.returnValue('test-token');
httpClient.get('/api/data').subscribe();
const req = httpTesting.expectOne('/api/data');
expect(req.request.headers.get('Authorization')).toBe('Bearer test-token');
req.flush({ data: 'test' });
});
it('should skip Authorization for public endpoints', () => {
authServiceSpy.getAccessToken.and.returnValue('test-token');
httpClient.get('/public/health').subscribe();
const req = httpTesting.expectOne('/public/health');
expect(req.request.headers.has('Authorization')).toBeFalse();
req.flush({ status: 'ok' });
});
});Test xác minh cả header injection và ngoại lệ public endpoint mà không thực hiện network request thực tế.
Pattern Production: Kết hợp Interceptor
Các ứng dụng thực tế xếp chồng nhiều interceptor. Thứ tự quan trọng: authentication nên chạy trước logging, và caching nên chạy trước retry logic.
Cấu hình production có thể như sau:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { authInterceptor } from './interceptors/auth.interceptor';
import { tokenRefreshInterceptor } from './interceptors/token-refresh.interceptor';
import { cacheInterceptor } from './interceptors/cache.interceptor';
import { retryInterceptor } from './interceptors/retry.interceptor';
import { loggingInterceptor } from './interceptors/logging.interceptor';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([
// Thứ tự: auth -> refresh -> cache -> retry -> logging
authInterceptor,
tokenRefreshInterceptor,
cacheInterceptor,
retryInterceptor,
loggingInterceptor,
])
),
],
});Thứ tự này đảm bảo token được đính kèm trước khi xử lý refresh, kiểm tra cache xảy ra trước retry, và logging capture timing cuối cùng.
Để tìm hiểu các pattern Angular rộng hơn, xem module cơ bản RxJS bao gồm các operator được sử dụng trong interceptor.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Điểm Chính cho Angular HttpClient Interceptor
- Functional interceptor với
withInterceptors()là tiêu chuẩn Angular 20, cung cấp thứ tự dễ dự đoán và tree-shaking tốt hơn so với các phương án dựa trên class - Sử dụng
inject()bên trong interceptor để truy cập service mà không cần boilerplate constructor - Clone request với
req.clone()để sửa đổi header vì đối tượngHttpRequestlà immutable - Token
HttpContextlàm cho hành vi theo từng request rõ ràng tại call site thay vì ẩn trong cấu hình global - Interceptor token refresh nên phối hợp concurrent request với
BehaviorSubjectđể ngăn chặn nhiều refresh call - Thứ tự interceptor quan trọng: authentication trước caching, caching trước retry, logging cuối cùng
Bạn có tìm ra lỗi trong Angular không?
Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Viết bởi
Anthony Fillion-MailletNgười sáng lập SharpSkill
Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.
Cập nhật ngày 22 tháng 8, 2026
Thẻ
Chia sẻ
Bài viết liên quan

Dependency Injection Nang Cao trong Angular 2026: Providers, Tokens va Cau Hoi Phong Van
Tim hieu sau ve he thong dependency injection cua Angular bao gom chien luoc provider, InjectionToken, injector phan cap va cac cau hoi phong van thuong gap cho developer Angular co kinh nghiem.

RxJS trong Angular 2026: Operator, Subject và Interop Signals
RxJS trong Angular 2026: nắm vững các operator, Subject và mẫu interop Signals mà lập trình viên Angular dùng trong production, cùng những câu hỏi phỏng vấn hay gặp nhất.

Angular 20 năm 2026: Resource API, httpResource và câu hỏi phỏng vấn
Angular 20 giới thiệu httpResource và ổn định hóa Resource API cho việc lấy dữ liệu dựa trên signal. Hướng dẫn thực hành về resource(), rxResource(), httpResource(), validation Zod và các câu hỏi phỏng vấn thường gặp.