Lesson 0015 · Frontend · ±15 menit

RxJS yang ditanyakan di interview Angular

Observable vs Promise, Subject/BehaviorSubject, operator inti, dan cara mencegah memory leak

Target lesson ini: Anda bisa menjawab pertanyaan interview "Apa beda Observable dan Promise, kapan Anda memakai switchMap, dan bagaimana Anda mencegah memory leak di Angular?" dalam 60 detik, lalu memilih operator yang tepat untuk skenario search box, request paralel, dan tombol Simpan.

1. Observable vs Promise

Anda sudah kenal Promise dari fetch(). RxJS menyebut Observable sebagai "lazy Push collections of multiple values" [1]: sama-sama push seperti Promise, tapi lazy dan bisa berisi banyak nilai. Dokumentasi Angular v16 merangkum bedanya [2]:

Promise (yang Anda kenal)Observable
Kapan jalan"Promises execute immediately on creation.""computation does not start until subscription."
Jumlah nilai"Promises provide one.""Observables provide many values."
Batal"Promises are not cancellable.""Observable subscriptions are cancellable."
Chaining.then() / .catch().pipe(operator...) lalu .subscribe()

Akibat praktisnya di Angular: HttpClient mengembalikan Observable, dan "the method doesn't begin its HTTP request until you call subscribe()". Sebaliknya, "Subscribing twice results in two HTTP requests" [3]. Lupa subscribe berarti tidak ada request sama sekali, sesuatu yang tidak pernah terjadi dengan fetch.

// React + fetch: langsung jalan
useEffect(() => { fetch('/api/products').then(r => r.json()).then(setProducts); }, []);

// Angular: baru jalan saat subscribe
this.http.get<Product[]>('/api/products')
  .pipe(map(list => list.filter(p => p.stock > 0)))   // seperti .then(transform)
  .subscribe(list => (this.products = list));          // seperti .then(setProducts)

2. Subject dan BehaviorSubject: state bersama di service

"An RxJS Subject is a special type of Observable that allows values to be multicasted to many Observers". "Every Subject is an Observable" dan "Every Subject is an Observer", jadi Anda bisa subscribe sekaligus memanggil next(v). BehaviorSubject "has a notion of 'the current value'": ia "stores the latest value emitted to its consumers" dan langsung mengirimkannya ke subscriber baru [4].

Inilah pola Angular Service untuk state bersama (padanan React: state di Context Provider, atau store Zustand):

@Injectable({ providedIn: 'root' })
export class CartService {
  private readonly items = new BehaviorSubject<Product[]>([]);   // wajib nilai awal
  readonly items$ = this.items.asObservable();                     // luar hanya bisa subscribe

  add(p: Product) { this.items.next([...this.items.value, p]); }  // seperti setState
}

Pakai Subject biasa untuk event (subscriber baru tidak butuh nilai lama), BehaviorSubject untuk state. ReplaySubject mengulang beberapa nilai lama ke subscriber baru [4].

3. Operator inti: empat yang hampir pasti ditanyakan

"Operators are functions" yang dirangkai di dalam pipe() [5]. map sama seperti Array.map, tapi untuk setiap nilai yang lewat. Yang membedakan kandidat adalah keluarga flattening: operator yang memetakan setiap nilai ke Observable baru (biasanya request HTTP).

OperatorSaat nilai baru datang dan request lama belum selesai…Skenario khas
switchMap"stops emitting items from the earlier-emitted inner Observable and begins emitting items from the new one" [6] (request lama dibatalkan)Search box, route param berubah
mergeMapSemua jalan bersamaan; parameter concurrent = "Maximum number of ObservableInputs being subscribed to concurrently" [7]Hapus/upload massal, urutan tidak penting
concatMapMengantre: "equivalent to mergeMap with concurrency parameter set to 1" [8]Simpan berurutan, urutan penting
exhaustMap"ignores every new projected Observable if the previous projected Observable has not yet completed" [9]Tombol Simpan / Login yang diklik berkali-kali

Tiga operator lain yang melengkapi jawaban:

Search box di dokumentasi Angular v16 memakai rangkaian ini [13] (disesuaikan ke Product Catalog):

this.results$ = this.searchControl.valueChanges.pipe(
  filter(text => text.length > 2),
  debounceTime(300),                 // tunggu user berhenti mengetik
  distinctUntilChanged(),            // abaikan kata kunci yang sama
  switchMap(term => this.products.search(term).pipe(
    catchError(() => of([]))         // satu request gagal tidak mematikan stream
  )),
);

Kenapa catchError ditaruh di dalam switchMap? Kalau error sampai ke stream luar, stream itu selesai dan search box berhenti bekerja.

4. Mencegah memory leak: async pipe dulu, takeUntil kalau perlu

Lesson 0013 sudah mengutip aturan Angular di ngOnDestroy: "Unsubscribe Observables and detach event handlers to avoid memory leaks" [14]. Subscription manual ke stream yang tidak pernah selesai (interval, valueChanges, BehaviorSubject di service) tetap hidup setelah component dihancurkan. Padanan React: useEffect tanpa fungsi cleanup.

  1. async pipe (pilihan utama). Ia "subscribes to an Observable or Promise and returns the latest value it has emitted", dan "When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks" [15].
    <li *ngFor="let p of results$ | async">{{ p.name }}</li>
  2. takeUntil (kalau harus subscribe di class). Ia meneruskan nilai "until a notifier Observable emits a value", lalu complete [16].
    private readonly destroy$ = new Subject<void>();
    
    ngOnInit() {
      this.cart.items$.pipe(takeUntil(this.destroy$))   // taruh di akhir pipe
        .subscribe(items => (this.count = items.length));
    }
    ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }

Di v16 juga ada takeUntilDestroyed() dari @angular/core/rxjs-interop, "Operator which completes the Observable when the calling context (component, directive, service, etc) is destroyed", tapi statusnya masih developer preview [17]. Sebut sebagai alternatif baru; jawab dengan async pipe dan takeUntil dulu.

Latihan 1 — Kuis konsep

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 — Pilih operatornya

Setiap soal adalah skenario dari Product Catalog. Sebelum mengklik, ucapkan alasannya dalam satu kalimat: "karena request lama harus…".

Latihan 3 — Ucapkan dengan suara keras

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

"Apa beda Observable dan Promise, kapan Anda memakai switchMap, dan bagaimana Anda mencegah memory leak di Angular?"
Bandingkan dengan jawaban contoh dan checklist-nya

"Promise langsung jalan saat dibuat, hanya menghasilkan satu nilai, dan tidak bisa dibatalkan. Observable lazy, baru jalan saat subscribe, bisa memancarkan banyak nilai, dan bisa dibatalkan dengan unsubscribe. HttpClient mengembalikan Observable. Untuk state bersama saya pakai BehaviorSubject di service, karena subscriber baru langsung dapat nilai terakhir. Operator saya rangkai di pipe: map untuk transformasi, catchError untuk error. Di search box saya pakai debounceTime lalu switchMap, karena switchMap membatalkan request lama sehingga hasil lama tidak menimpa yang baru. Kalau semua request harus jalan paralel saya pakai mergeMap, dan forkJoin untuk menunggu beberapa request sekaligus, seperti Promise.all. Untuk mencegah memory leak saya utamakan async pipe yang unsubscribe otomatis. Kalau harus subscribe manual, saya pakai takeUntil(destroy$) yang di-next() di ngOnDestroy."

Checklist: apakah Anda menyebut…

  • Observable lazy dan bisa banyak nilai, sedangkan Promise eager dan satu nilai?
  • Observable bisa dibatalkan (unsubscribe), Promise tidak?
  • BehaviorSubject untuk state bersama karena menyimpan nilai terakhir?
  • switchMap membatalkan request lama (search box), bersama debounceTime?
  • beda switchMap dengan mergeMap (paralel) atau forkJoin (tunggu semua)?
  • async pipe atau takeUntil + ngOnDestroy untuk mencegah memory leak?

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

Bacaan utama (±15 menit) RxJS: Observable, panduan resmi RxJS. Baca bagian "Pull versus Push" dan "Disposing Observable Executions". Kalau masih ada waktu, lanjut ke Angular v16: Observables compared to other techniques.

Ringkasan untuk dicetak: Reference: RxJS untuk interview Angular 16 · Istilah: GLOSSARY.md

Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "apakah subscription ke HttpClient juga wajib di-unsubscribe?" atau "kapan saya pakai combineLatest dan bukan forkJoin?"