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 dan Interceptor untuk penanganan HTTP request

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.

Functional Interceptor Adalah Standar

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:

main.tstypescript
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:

interceptors/logging.interceptor.tstypescript
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:

interceptors/auth.interceptor.tstypescript
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:

interceptors/retry.interceptor.tstypescript
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:

interceptors/cache.interceptor.tstypescript
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:

services/user.service.tstypescript
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:

interceptors/token-refresh.interceptor.tstypescript
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:

interceptors/auth.interceptor.spec.tstypescript
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:

main.tstypescript
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 objek HttpRequest bersifat immutable
  • Token HttpContext membuat perilaku per-request eksplisit di call site daripada tersembunyi dalam konfigurasi global
  • Interceptor token refresh harus mengkoordinasikan concurrent request dengan BehaviorSubject untuk mencegah multiple refresh call
  • Urutan interceptor penting: autentikasi sebelum caching, caching sebelum retry, logging terakhir
Tantangan harian

Bisakah kamu menemukan bug di Angular?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri 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

#angular
#httpclient
#interceptor
#http
#typescript

Bagikan

Artikel terkait