Angular HttpClient และ Interceptor ในปี 2026: การจัดการ Request และคำถามสัมภาษณ์
เรียนรู้วิธีใช้ Angular HttpClient และ functional interceptor สำหรับการจัดการ HTTP request, การยืนยันตัวตนด้วย token, retry logic และ caching พร้อมคำถามสัมภาษณ์ Angular ที่พบบ่อย

Angular HttpClient interceptor เปลี่ยนแปลงวิธีที่แอปพลิเคชันจัดการ HTTP request ตั้งแต่การเพิ่ม header สำหรับการยืนยันตัวตนไปจนถึงการใช้งาน retry logic Angular 20 ได้กำหนดมาตรฐาน functional interceptor เป็นแนวทางที่แนะนำ โดยแทนที่รูปแบบ class-based ด้วย API ที่คาดเดาได้มากขึ้นและสามารถ tree-shake ได้ดีกว่า
Angular 20 แนะนำให้ใช้ functional interceptor ด้วย withInterceptors() แทน class-based interceptor เนื่องจาก functional interceptor มีลำดับการทำงานที่คาดเดาได้มากกว่าและผสานกับ standalone component ได้ดีกว่า
การตั้งค่า HttpClient ด้วย provideHttpClient
ก่อนเขียน interceptor จำเป็นต้องกำหนดค่า HttpClient ในการ bootstrap แอปพลิเคชัน สถาปัตยกรรม standalone ของ Angular ใช้ provideHttpClient() แทน HttpClientModule แบบเดิม
การตั้งค่าทำในการกำหนดค่าแอปพลิเคชัน:
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 ทำงานตามลำดับใน array
withInterceptors([authInterceptor, loggingInterceptor])
),
],
});Interceptor ทำงานตามลำดับที่กำหนดใน array โดย interceptor ตัวแรกจะประมวลผล outgoing request ก่อน และ interceptor ตัวสุดท้ายจะเห็น response ก่อน
โครงสร้างของ Functional Interceptor
Functional interceptor รับ request และฟังก์ชัน next การเรียก next(req) จะส่ง request ไปยัง interceptor ถัดไปใน chain หรือไปยัง backend หากเป็น interceptor ตัวสุดท้าย
โครงสร้างพื้นฐานมีลักษณะดังนี้:
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 นี้บันทึก method และ URL ของ request จากนั้นวัดเวลา response โดย operator tap จะสังเกต response โดยไม่แก้ไขมัน
Authentication Interceptor พร้อม Token Injection
แอปพลิเคชันส่วนใหญ่ต้องแนบ authentication token กับ outgoing request โดย interceptor จะ clone request พร้อม header Authorization เนื่องจากอ็อบเจกต์ HttpRequest เป็น immutable
รูปแบบนี้ดึง token จาก service และแนบมัน:
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 โดยใช้ฟังก์ชัน inject() ของ Angular
const authService = inject(AuthService);
const token = authService.getAccessToken();
// ข้าม auth header สำหรับ public endpoint
if (req.url.includes('/public/') || !token) {
return next(req);
}
// Clone request พร้อม header Authorization
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
return next(authReq);
};ฟังก์ชัน inject() ทำงานได้เพราะ interceptor ทำงานภายใน injection context ของ Angular ซึ่งช่วยลด boilerplate ของ constructor injection จาก class-based interceptor
การจัดการ Error และ Retry Logic
ความล้มเหลวของเครือข่ายสามารถเกิดขึ้นได้ตลอดเวลา Retry interceptor สามารถลองใหม่โดยอัตโนมัติสำหรับ request ที่ล้มเหลวด้วย exponential backoff ซึ่งช่วยเพิ่มความน่าเชื่อถือโดยไม่ต้องเปลี่ยนแปลงทุก HTTP call
Operator ของ RxJS จัดการ retry logic:
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { retry, timer } from 'rxjs';
export const retryInterceptor: HttpInterceptorFn = (req, next) => {
// Retry เฉพาะ GET request (idempotent)
if (req.method !== 'GET') {
return next(req);
}
return next(req).pipe(
retry({
count: 3,
delay: (error: HttpErrorResponse, retryCount: number) => {
// ไม่ 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 นี้ retry เฉพาะ GET request เท่านั้น เนื่องจาก POST, PUT และ DELETE ไม่ใช่ idempotent ส่วน client error (4xx) จะข้าม retry เพราะ server ปฏิเสธ request อย่างชัดเจนแล้ว
พร้อมที่จะพิชิตการสัมภาษณ์ Angular แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
Response Caching ด้วย HttpContext
HttpClient มี HttpContext สำหรับส่ง metadata ระหว่าง interceptor และโค้ดที่เรียกใช้ Caching interceptor สามารถอ่าน context token เพื่อกำหนดพฤติกรรม cache
Cache token และ interceptor ทำงานร่วมกัน:
import { HttpInterceptorFn, HttpContextToken, HttpResponse } from '@angular/common/http';
import { of, tap } from 'rxjs';
// Context token สำหรับควบคุม caching ต่อ request
export const CACHE_REQUEST = new HttpContextToken<boolean>(() => false);
const cache = new Map<string, HttpResponse<unknown>>();
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
// Cache เฉพาะเมื่อร้องขออย่างชัดเจนผ่าน 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());
}
})
);
};โค้ดที่เรียกใช้เลือกเปิด caching ด้วย 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),
});
}
}รูปแบบนี้ทำให้พฤติกรรม caching ชัดเจนที่ call site แทนที่จะซ่อนอยู่ในการกำหนดค่า global
Token Refresh พร้อมการจัดการ 401
เมื่อ access token หมดอายุ server จะส่ง response 401 กลับมา Interceptor สามารถจับสิ่งนี้ refresh token และลอง request เดิมใหม่อย่างโปร่งใส
Logic การ refresh ประสานงานหลาย 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) {
// รอให้ refresh เสร็จ
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 ป้องกันการเรียก refresh หลายครั้งเมื่อหลาย request ล้มเหลวพร้อมกัน หลังจาก token ถูก refresh แล้ว request ที่รอทั้งหมดจะลองใหม่ด้วย token ใหม่
คำถามสัมภาษณ์: HttpClient และ Interceptor
การสัมภาษณ์ทางเทคนิคมักทดสอบความเข้าใจเกี่ยวกับรูปแบบการจัดการ HTTP คำถามเหล่านี้ปรากฏบ่อยในตำแหน่ง Angular
Q: ทำไม functional interceptor จึงถูกแนะนำมากกว่า class-based interceptor?
Functional interceptor มีลำดับการทำงานที่คาดเดาได้มากกว่า เอกสาร Angular ระบุว่า functional interceptor ด้วย withInterceptors() รักษาลำดับ array อย่างเคร่งครัด ในขณะที่ DI-based interceptor อาจมีปัญหาเรื่องลำดับในลำดับชั้น module ที่ซับซ้อน Functional interceptor ยังสร้าง bundle ที่เล็กกว่าเพราะ tree-shake ได้ดีกว่า
Q: จะป้องกันไม่ให้ interceptor ทำงานกับ request บางตัวได้อย่างไร?
มีสองวิธี วิธีแรก ตรวจสอบรูปแบบ URL ใน interceptor และเรียก next(req) โดยไม่แก้ไขสำหรับ path ที่ต้องการยกเว้น วิธีที่สอง ใช้ token HttpContext ที่โค้ดเรียกใช้ตั้งค่าเพื่อ opt-out จากพฤติกรรม interceptor วิธี context จะเก็บการตัดสินใจยกเว้นไว้ที่ call site
Q: จะเกิดอะไรขึ้นถ้าลืมเรียก next() ใน interceptor?
Request จะไม่ถึง server Observable ที่ส่งกลับโดย next() แสดงถึงส่วนที่เหลือของ interceptor chain และ HTTP call จริง ถ้าไม่เรียกมัน interceptor downstream และ backend จะไม่เห็น request นี้เป็นการออกแบบโดยตั้งใจสำหรับ caching interceptor ที่ส่งคืน cached response
Q: จะจัดการ concurrent request ระหว่างการ token refresh ได้อย่างไร?
ใช้ BehaviorSubject เพื่อประสานงาน เมื่อ 401 แรกเรียก refresh ให้ตั้ง flag และ emit null บน subject 401 ถัดไปจะรอบน subject ด้วย filter จนกว่า token ใหม่จะมาถึง วิธีนี้ป้องกันการเรียก refresh หลายครั้งและรับประกันว่า pending request ทั้งหมดจะลองใหม่ด้วย token ใหม่เดียวกัน
Q: Interceptor สามารถแก้ไข response body ได้หรือไม่?
ได้ ใช้ map() บน Observable ที่ส่งกลับโดย next() เพื่อแปลงอ็อบเจกต์ HttpResponse เนื่องจาก response เป็น immutable จึงต้อง clone พร้อม body ที่แก้ไขแล้ว รูปแบบนี้ใช้ได้สำหรับการเพิ่ม computed field, unwrap API envelope หรือ normalize โครงสร้างข้อมูล
สำหรับการเตรียมสัมภาษณ์ Angular เพิ่มเติม ดู โมดูลคำถามสัมภาษณ์ HttpClient
การทดสอบ Functional Interceptor
Interceptor ต้องการการทดสอบด้วย mocked HTTP handler HttpTestingController ของ Angular ทำงานกับ interceptor ที่ลงทะเบียนผ่าน provideHttpClient()
การตั้งค่าการทดสอบมีลักษณะดังนี้:
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' });
});
});การทดสอบตรวจสอบทั้ง header injection และข้อยกเว้น public endpoint โดยไม่ทำ network request จริง
รูปแบบ Production: การรวม Interceptor
แอปพลิเคชันจริงซ้อนหลาย interceptor ลำดับสำคัญ: authentication ควรทำงานก่อน logging และ caching ควรทำงานก่อน retry logic
การกำหนดค่า production อาจมีลักษณะดังนี้:
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([
// ลำดับ: auth -> refresh -> cache -> retry -> logging
authInterceptor,
tokenRefreshInterceptor,
cacheInterceptor,
retryInterceptor,
loggingInterceptor,
])
),
],
});ลำดับนี้รับประกันว่า token จะถูกแนบก่อนการจัดการ refresh, การตรวจสอบ cache เกิดขึ้นก่อน retry และ logging จับ timing สุดท้าย
สำหรับรูปแบบ Angular ที่กว้างขึ้น ดู โมดูลพื้นฐาน RxJS ซึ่งครอบคลุม operator ที่ใช้ใน interceptor
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
ประเด็นสำคัญสำหรับ Angular HttpClient Interceptor
- Functional interceptor ด้วย
withInterceptors()เป็นมาตรฐาน Angular 20 มีลำดับที่คาดเดาได้และ tree-shaking ที่ดีกว่าทางเลือก class-based - ใช้
inject()ภายใน interceptor เพื่อเข้าถึง service โดยไม่ต้องมี boilerplate constructor - Clone request ด้วย
req.clone()เพื่อแก้ไข header เนื่องจากอ็อบเจกต์HttpRequestเป็น immutable - Token
HttpContextทำให้พฤติกรรมต่อ request ชัดเจนที่ call site แทนที่จะซ่อนในการกำหนดค่า global - Interceptor สำหรับ token refresh ควรประสานงาน concurrent request ด้วย
BehaviorSubjectเพื่อป้องกันการเรียก refresh หลายครั้ง - ลำดับ interceptor สำคัญ: authentication ก่อน caching, caching ก่อน retry, logging สุดท้าย
คุณหาบั๊กใน Angular เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 22 สิงหาคม 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Dependency Injection ขั้นสูงใน Angular 2026: Providers, Tokens และคำถามสัมภาษณ์
เรียนรู้ระบบ dependency injection ของ Angular อย่างลึกซึ้ง รวมถึงกลยุทธ์ provider, InjectionToken, injector แบบลำดับชั้น และคำถามสัมภาษณ์ที่พบบ่อยสำหรับนักพัฒนา Angular ที่มีประสบการณ์

RxJS ใน Angular 2026: โอเปอเรเตอร์ Subject และ interop Signals
RxJS ใน Angular 2026: เชี่ยวชาญโอเปอเรเตอร์ Subject และรูปแบบ interop Signals ที่นักพัฒนา Angular ใช้จริงในโปรดักชัน พร้อมคำถามสัมภาษณ์ที่พบบ่อยที่สุด

Angular 20 ในปี 2026: Resource API, httpResource และคำถามสัมภาษณ์
Angular 20 เปิดตัว httpResource และทำให้ Resource API เสถียรสำหรับการดึงข้อมูลแบบอิงกับ signal บทเรียนเชิงปฏิบัติครอบคลุม resource(), rxResource(), httpResource(), การตรวจสอบด้วย Zod และคำถามสัมภาษณ์ที่พบบ่อย