Angular HttpClient dan Interceptor di 2026: Penanganan Request dan Pertanyaan Interview
Pelajari cara menggunakan Angular HttpClient dan functional interceptor untuk menangani HTTP request, autentikasi token, retry logic, dan caching. Dilengkapi pertanyaan interview Angular yang sering muncul.

Angular HttpClient interceptor mengubah cara aplikasi menangani HTTP request, mulai dari menambahkan header autentikasi hingga mengimplementasikan retry logic. Angular 20 telah menstandarkan functional interceptor sebagai pendekatan yang direkomendasikan, menggantikan pola berbasis class dengan API yang lebih dapat diprediksi dan tree-shakable.
Angular 20 merekomendasikan functional interceptor dengan withInterceptors() dibandingkan class-based interceptor. Functional interceptor memiliki urutan eksekusi yang lebih dapat diprediksi dan terintegrasi lebih baik dengan standalone component.
Setup HttpClient dengan provideHttpClient
Sebelum menulis interceptor, HttpClient harus dikonfigurasi dalam bootstrap aplikasi. Arsitektur standalone Angular menggunakan provideHttpClient() sebagai pengganti HttpClientModule yang lama.
Setup dilakukan di konfigurasi aplikasi:
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 dieksekusi sesuai urutan array
withInterceptors([authInterceptor, loggingInterceptor])
),
],
});Interceptor dieksekusi sesuai urutan yang ditentukan dalam array. Interceptor pertama memproses outgoing request terlebih dahulu, dan interceptor terakhir melihat response terlebih dahulu.
Anatomi Functional Interceptor
Functional interceptor menerima request dan fungsi next. Memanggil next(req) meneruskan request ke interceptor berikutnya dalam chain atau ke backend jika ini adalah interceptor terakhir.
Struktur dasarnya seperti berikut:
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 ini mencatat method dan URL request, kemudian mengukur response time. Operator tap mengobservasi response tanpa memodifikasinya.
Authentication Interceptor dengan Token Injection
Sebagian besar aplikasi perlu melampirkan authentication token ke outgoing request. Interceptor meng-clone request dengan header Authorization, karena objek HttpRequest bersifat immutable.
Pola ini mengambil token dari service dan melampirkannya:
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 menggunakan fungsi inject() Angular
const authService = inject(AuthService);
const token = authService.getAccessToken();
// Lewati auth header untuk public endpoint
if (req.url.includes('/public/') || !token) {
return next(req);
}
// Clone request dengan header Authorization
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
return next(authReq);
};Fungsi inject() berfungsi karena interceptor dieksekusi dalam injection context Angular. Ini menghilangkan boilerplate constructor injection dari class-based interceptor.
Error Handling dan Retry Logic
Kegagalan jaringan bisa terjadi kapan saja. Retry interceptor dapat secara otomatis mencoba ulang request yang gagal dengan exponential backoff, meningkatkan reliabilitas tanpa memerlukan perubahan pada setiap HTTP call.
Operator RxJS menangani retry logic:
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { retry, timer } from 'rxjs';
export const retryInterceptor: HttpInterceptorFn = (req, next) => {
// Hanya retry GET request (idempotent)
if (req.method !== 'GET') {
return next(req);
}
return next(req).pipe(
retry({
count: 3,
delay: (error: HttpErrorResponse, retryCount: number) => {
// Jangan 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 ini hanya mencoba ulang GET request karena POST, PUT, dan DELETE tidak bersifat idempotent. Client error (4xx) melewati retry karena server secara eksplisit menolak request tersebut.
Siap menguasai wawancara Angular Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Response Caching dengan HttpContext
HttpClient menyediakan HttpContext untuk meneruskan metadata antara interceptor dan kode pemanggil. Caching interceptor dapat membaca context token untuk menentukan perilaku cache.
Cache token dan interceptor bekerja bersama:
import { HttpInterceptorFn, HttpContextToken, HttpResponse } from '@angular/common/http';
import { of, tap } from 'rxjs';
// Context token untuk mengontrol caching per-request
export const CACHE_REQUEST = new HttpContextToken<boolean>(() => false);
const cache = new Map<string, HttpResponse<unknown>>();
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
// Hanya cache jika diminta secara eksplisit via 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());
}
})
);
};Kode pemanggil memilih untuk mengaktifkan caching dengan 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),
});
}
}Pola ini membuat perilaku caching eksplisit di call site daripada tersembunyi dalam konfigurasi global.
Token Refresh dengan Penanganan 401
Ketika access token kedaluwarsa, server mengembalikan response 401. Interceptor dapat menangkap ini, me-refresh token, dan mencoba ulang request asli secara transparan.
Logic refresh mengkoordinasikan beberapa 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) {
// Tunggu refresh selesai
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 mencegah multiple refresh call ketika beberapa request gagal secara bersamaan. Setelah token di-refresh, semua request yang menunggu mencoba ulang dengan token baru.
Pertanyaan Interview: HttpClient dan Interceptor
Interview teknis sering menguji pemahaman tentang pola penanganan HTTP. Pertanyaan-pertanyaan ini sering muncul dalam posisi Angular.
Q: Mengapa functional interceptor lebih direkomendasikan dibandingkan class-based interceptor?
Functional interceptor memiliki urutan eksekusi yang lebih dapat diprediksi. Dokumentasi Angular menyatakan bahwa functional interceptor dengan withInterceptors() mempertahankan urutan array yang ketat, sementara DI-based interceptor dapat memiliki masalah pengurutan dalam hierarki modul yang kompleks. Functional interceptor juga menghasilkan bundle yang lebih kecil karena tree-shake lebih baik.
Q: Bagaimana cara mencegah interceptor berjalan pada request tertentu?
Ada dua pendekatan. Pertama, periksa pola URL di interceptor dan panggil next(req) tanpa modifikasi untuk path yang dikecualikan. Kedua, gunakan token HttpContext yang diatur kode pemanggil untuk opt-out dari perilaku interceptor. Pendekatan context menjaga keputusan pengecualian di call site.
Q: Apa yang terjadi jika lupa memanggil next() di interceptor?
Request tidak pernah mencapai server. Observable yang dikembalikan oleh next() mewakili sisa interceptor chain dan HTTP call yang sebenarnya. Tanpa memanggilnya, interceptor downstream dan backend tidak pernah melihat request. Ini disengaja untuk caching interceptor yang mengembalikan cached response.
Q: Bagaimana cara menangani concurrent request selama token refresh?
Gunakan BehaviorSubject untuk koordinasi. Ketika 401 pertama memicu refresh, atur flag dan emit null pada subject. 401 berikutnya menunggu pada subject dengan filter sampai token baru tiba. Ini mencegah multiple refresh call dan memastikan semua pending request mencoba ulang dengan token fresh yang sama.
Q: Bisakah interceptor memodifikasi response body?
Ya. Gunakan map() pada Observable yang dikembalikan oleh next() untuk mentransformasi objek HttpResponse. Karena response bersifat immutable, clone dengan body yang dimodifikasi. Pola ini berfungsi untuk menambahkan computed field, membuka API envelope, atau menormalisasi struktur data.
Untuk persiapan interview Angular lebih lanjut, lihat modul pertanyaan interview HttpClient.
Testing Functional Interceptor
Interceptor memerlukan testing dengan mocked HTTP handler. HttpTestingController Angular bekerja dengan interceptor yang didaftarkan melalui provideHttpClient().
Setup test seperti berikut:
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 memverifikasi baik header injection maupun pengecualian public endpoint tanpa melakukan network request yang sebenarnya.
Pola Produksi: Menggabungkan Interceptor
Aplikasi nyata menumpuk beberapa interceptor. Urutan penting: autentikasi harus berjalan sebelum logging, dan caching harus berjalan sebelum retry logic.
Konfigurasi produksi mungkin seperti ini:
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([
// Urutan: auth -> refresh -> cache -> retry -> logging
authInterceptor,
tokenRefreshInterceptor,
cacheInterceptor,
retryInterceptor,
loggingInterceptor,
])
),
],
});Urutan ini memastikan token dilampirkan sebelum penanganan refresh, pemeriksaan cache terjadi sebelum retry, dan logging menangkap timing akhir.
Untuk pola Angular yang lebih luas, lihat modul dasar-dasar RxJS yang mencakup operator yang digunakan dalam interceptor.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Poin Penting untuk Angular HttpClient Interceptor
- Functional interceptor dengan
withInterceptors()adalah standar Angular 20, menawarkan urutan yang dapat diprediksi dan tree-shaking yang lebih baik dibandingkan alternatif berbasis class - Gunakan
inject()di dalam interceptor untuk mengakses service tanpa boilerplate constructor - Clone request dengan
req.clone()untuk memodifikasi header karena objekHttpRequestbersifat immutable - Token
HttpContextmembuat perilaku per-request eksplisit di call site daripada tersembunyi dalam konfigurasi global - Interceptor token refresh harus mengkoordinasikan concurrent request dengan
BehaviorSubjectuntuk mencegah multiple refresh call - Urutan interceptor penting: autentikasi sebelum caching, caching sebelum retry, logging terakhir
Bisakah kamu menemukan bug di Angular?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 22 Agustus 2026
Tag
Bagikan
Artikel terkait

Dependency Injection Lanjutan di Angular 2026: Providers, Tokens, dan Pertanyaan Interview
Pelajari sistem dependency injection Angular secara mendalam termasuk strategi provider, InjectionToken, injector hierarkis, dan pertanyaan interview umum untuk developer Angular berpengalaman.

RxJS di Angular 2026: Operator, Subject, dan Interop Signals
RxJS di Angular 2026: kuasai operator, Subject, dan pola interop Signals yang dipakai developer Angular di produksi, plus pertanyaan wawancara yang paling sering muncul.

Angular 20 di 2026: Resource API, httpResource, dan Pertanyaan Wawancara
Angular 20 memperkenalkan httpResource dan menstabilkan Resource API untuk pengambilan data berbasis signal. Tutorial praktis yang mencakup resource(), rxResource(), httpResource(), validasi Zod, dan pertanyaan wawancara umum.