Lesson 0016 · Frontend · ±15 menit

Reactive Forms, HttpClient, dan HTTP Interceptor

Dari form edit Product, lewat request HTTP, sampai satu tempat untuk header dan error global

Target lesson ini: Anda bisa menjawab pertanyaan interview "Kapan Anda memilih Reactive Forms dibanding template-driven, apa yang dikembalikan HttpClient, dan apa yang Anda taruh di HTTP Interceptor?" dalam 60 detik.

1. Reactive vs template-driven forms

Angular punya dua pendekatan form. Keduanya memakai kelas dasar yang sama: FormControl "Tracks the value and validation status of an individual form control", dan FormGroup melakukan hal yang sama "for a collection of form controls" [1]. Bedanya ada di siapa yang membuat object itu:

ReactiveTemplate-drivenPadanan React
Form model"Explicit, created in component class""Implicit, created by directives" (ngModel)controlled input + state / React Hook Form
Data model"Structured and immutable""Unstructured and mutable"–
Data flow"Synchronous""Asynchronous"–
Validasi"Functions""Directives"fungsi validasi / schema Zod

Sumber tabel: Angular v16: Key differences [1].

Alasan memilih yang harus Anda ucapkan: Reactive Forms "are more robust: they're more scalable, reusable, and testable", sedangkan template-driven "are useful for adding a simple form to an app, such as an email list signup form" [1]. Reactive Forms juga "can be tested without rendering the UI", sedangkan test template-driven "requires a detailed knowledge of the change detection process" [1].

// product-form.component.ts  (AppModule meng-import ReactiveFormsModule)
export class ProductFormComponent {
  form = this.fb.group({
    name:  ['', Validators.required],
    price: [0,  [Validators.required, Validators.min(0.01)]],   // validator = fungsi
  });
  constructor(private fb: FormBuilder, private products: ProductService) {}

  save() {
    if (this.form.invalid) return;
    this.products.create(this.form.getRawValue()).subscribe();
  }
}
<form [formGroup]="form" (ngSubmit)="save()">
  <input formControlName="name">
  <input formControlName="price" type="number">
  <button type="submit" [disabled]="form.invalid">Simpan</button>
</form>

<!-- template-driven (FormsModule): model implisit, two-way binding -->
<input name="name" [(ngModel)]="product.name" required>

FormBuilder hanya pembuat singkat: "factory methods for generating instances" control(), group(), dan array(). Setiap control punya valueChanges, Observable yang bisa Anda pipe dengan operator dari lesson 0015 [2].

2. HttpClient: semuanya Observable

"The HttpClient service makes use of observables for all transactions" [3]. Di aplikasi NgModule, HttpClientModule di-import di AppModule, lalu HttpClient di-inject ke Angular Service. Tiga hal yang membedakannya dari fetch/Axios:

  1. Lazy. "the method doesn't begin its HTTP request until you call subscribe()" [4]. Pakai async pipe di template.
  2. Generic hanya untuk compiler. get<Product[]>() adalah type assertion: "This is a build-time check and doesn't guarantee that the server actually responds with an object of this type." Karena itu pakai interface, bukan class [4].
  3. Error = HttpErrorResponse. Error dari server (404, 500) dan error jaringan sama-sama ditangkap di HttpErrorResponse; error jaringan "have status set to 0" [5].
@Injectable({ providedIn: 'root' })
export class ProductService {
  constructor(private http: HttpClient) {}

  getAll(): Observable<Product[]> {
    return this.http.get<Product[]>('/api/products').pipe(
      retry(2),                                    // subscribe ulang = request ulang
      catchError((e: HttpErrorResponse) => throwError(() => new Error(`Gagal (${e.status})`)))
    );
  }
  create(dto: CreateProductDto) { return this.http.post<Product>('/api/products', dto); }
}

3. HTTP Interceptor: satu tempat untuk setiap request

Interceptor "inspect and transform HTTP requests from your application to a server" dan juga response-nya. Tanpa interceptor, tugas seperti autentikasi dan logging harus ditulis "explicitly for each HttpClient method call" [6]. Dokumentasi Angular sendiri menyebutnya "a common middleware pattern found in frameworks such as Express.js" [6]. Padanan React yang paling dekat: axios.interceptors.request.use(...) [7].

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService, private router: Router) {}

  intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    const authReq = req.clone({ setHeaders: { Authorization: `Bearer ${this.auth.token}` } });
    return next.handle(authReq).pipe(                // Observable response
      catchError((e: HttpErrorResponse) => {
        if (e.status === 401) this.router.navigate(['/login']);   // error global
        return throwError(() => e);
      })
    );
  }
}

// app.module.ts
providers: [{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }]

Empat aturan yang sering ditanyakan:

Angular 16 juga punya interceptor berbentuk fungsi (HttpInterceptorFn, didaftarkan lewat provideHttpClient(withInterceptors([...]))), yang "are executed in an injection context" sehingga bisa memakai inject() [9]. Itu pola aplikasi standalone; di proyek NgModule jawab dengan class + HTTP_INTERCEPTORS dulu. Detail JWT, CORS, dan error handling di API dibahas di lesson 0017.

Latihan 1 — Reactive atau template-driven?

Jawab tanpa scroll ke atas; kalau ragu, tebak dulu, lalu baca penjelasannya.

Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil di mock interview 0018.

Latihan 2 — HttpClient dan interceptor

Sebelum mengklik, ucapkan alasannya dalam satu kalimat.

Latihan 3 — Ucapkan dengan suara keras

Pasang timer 60 detik. Jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.

"Kapan Anda memilih Reactive Forms dibanding template-driven, apa yang dikembalikan HttpClient, dan apa yang Anda taruh di HTTP Interceptor?"
Bandingkan dengan jawaban contoh dan checklist-nya

"Reactive Forms membuat form model secara eksplisit di class component dengan FormGroup dan FormControl; template-driven membuatnya implisit lewat ngModel. Untuk form besar dengan validasi dan field dinamis saya pilih Reactive: alurnya sinkron, validatornya fungsi, dan bisa di-unit test tanpa render. Template-driven cukup untuk form kecil. HttpClient mengembalikan Observable yang baru mengirim request saat di-subscribe; generic get<Product[]> hanya type assertion saat compile. Error datang sebagai HttpErrorResponse dan saya tangani dengan catchError. Interceptor adalah class yang mengimplementasikan HttpInterceptor: karena request immutable, saya clone lalu setHeaders Authorization, kemudian next.handle. Saya daftarkan dengan HTTP_INTERCEPTORS dan multi: true; urutannya mengikuti urutan provider. Di sana juga saya tangani 401 secara global. Padanannya interceptor Axios, atau middleware Express di sisi backend."

Checklist: apakah Anda menyebut…

  • Reactive: model eksplisit di class (FormGroup/FormControl); template-driven: implisit lewat ngModel?
  • alasan memilih: Reactive scalable, sinkron, validator berupa fungsi, mudah diuji; template-driven untuk form sederhana?
  • HttpClient mengembalikan Observable yang baru request saat subscribe?
  • generic get<T> hanya type assertion compile-time; error = HttpErrorResponse, ditangani catchError?
  • interceptor: clone request (immutable) + setHeaders Authorization, lalu next.handle?
  • didaftarkan lewat HTTP_INTERCEPTORS multi: true, urutan = urutan provider; dipakai untuk error global (401), padanan interceptor Axios?

Kalau kurang dari 4 poin, ulangi sebelum lesson 0017. Soal ini juga akan muncul lagi di mock interview 0018.

Bacaan utama (±15 menit) Angular v16: HTTP - Intercept requests and responses, dokumentasi resmi Angular 16. Kalau masih ada waktu, baca tabel "Key differences" di Angular v16: Introduction to forms.

Ringkasan untuk dicetak: Reference: Forms, HttpClient, dan HTTP Interceptor · Istilah: GLOSSARY.md

Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "bagaimana menulis unit test untuk interceptor dengan HttpTestingController?" atau "kapan saya pakai patchValue dan bukan setValue?"