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.

RxJS di Angular 2026 operator, Subject, dan interop Signals

RxJS di Angular tetap menjadi tulang punggung pemrograman asinkron pada tahun 2026, bahkan ketika Signals mengubah cara komponen Angular melacak state sinkron. Mengetahui kapan harus memilih Observable, kapan Signal lebih cocok, dan bagaimana keduanya saling beroperasi kini menjadi keterampilan inti bagi setiap developer Angular. Pembahasan mendalam ini menelusuri operator, Subject, dan pola interop yang membedakan jawaban wawancara yang meyakinkan dari yang tersendat.

Signals vs Observables dalam satu kalimat

Signals memodelkan state sinkron yang selalu memiliki nilai saat ini; Observables memodelkan aliran event asinkron sepanjang waktu. Angular 20 mempertahankan keduanya, dan paket @angular/core/rxjs-interop adalah jembatan resmi di antara keduanya.

Mengapa RxJS tetap penting berdampingan dengan Angular Signals

Kehadiran Signals membuat banyak tim bertanya apakah RxJS akan segera ditinggalkan. Jawabannya, tiga versi mayor kemudian, adalah tidak. Signals unggul dalam merepresentasikan state yang memiliki nilai saat ini: sebuah penghitung, sebuah field formulir, sebuah tab yang dipilih. Observables unggul dalam memodelkan event yang tiba sepanjang waktu dan mungkin perlu ditransformasi, dibatalkan, atau digabungkan: respons HTTP, pesan WebSocket, input keyboard, event router, dan timer.

API bawaan Angular sendiri membuat pembagian ini terlihat jelas. HttpClient masih mengembalikan Observables. Reactive forms mengekspos valueChanges dan statusChanges sebagai Observables. Event router mengalir melalui sebuah Observable. Di mana pun beberapa event asinkron mengalir melalui kanal yang sama, operator RxJS tetap menjadi alat paling ekspresif yang tersedia. Panduan reaktivitas Angular resmi memperlakukan kedua model tersebut sebagai saling melengkapi, bukan bersaing, dan dokumentasi RxJS tetap menjadi rujukan wajib untuk setiap aliran data yang tidak sepele.

Aturan praktis yang bertahan di code review: gunakan Signals untuk state komponen lokal yang dibaca di template, dan gunakan Observables untuk pipeline asinkron yang membutuhkan operator. Lapisan interop menghubungkan keduanya sehingga tidak ada model yang menjadi tembok penghalang.

Operator kombinasi memperkuat argumen ini. Mengoordinasikan beberapa sumber asinkron, seperti menggabungkan parameter rute dengan filter tersimpan milik pengguna dan feed harga langsung, adalah persis untuk apa combineLatest, withLatestFrom, dan forkJoin dibangun. Signals dapat menurunkan nilai terkomputasi dari signal lain, tetapi tidak dapat mengekspresikan koordinasi berbasis waktu seperti "hanya pancarkan setelah ketiga sumber menghasilkan setidaknya satu nilai." Komposisi deklaratif sepanjang waktu itulah alasan abadi mengapa RxJS tetap ada di dalam kotak peralatan.

Operator RxJS inti yang diandalkan setiap developer Angular

Operator adalah fungsi murni yang mengambil sebuah Observable dan mengembalikan yang baru. Skenario wawancara klasik adalah search-as-you-type, karena ia melatih empat operator sekaligus: membatasi laju input, melewati duplikat, membatalkan permintaan usang, dan pulih dari kesalahan.

search.component.tstypescript
import { Component, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { debounceTime, distinctUntilChanged, switchMap, catchError } from 'rxjs/operators';
import { of } from 'rxjs';

@Component({
  selector: 'app-search',
  imports: [ReactiveFormsModule],
  template: `<input [formControl]="query" placeholder="Search repositories" />`,
})
export class SearchComponent {
  private http = inject(HttpClient);
  query = new FormControl('');

  results$ = this.query.valueChanges.pipe(
    debounceTime(300),               // wait until typing pauses for 300ms
    distinctUntilChanged(),          // ignore emissions that repeat the last value
    switchMap((term) =>              // cancel the in-flight request, start a fresh one
      this.http.get(`/api/search?q=${term}`).pipe(
        catchError(() => of([]))     // recover from a failed request without killing the stream
      )
    )
  );
}

Detail halus yang diselidiki pewawancara adalah mengapa switchMap berada di level terluar sementara catchError bersarang di dalamnya. Menempatkan catchError pada Observable HTTP di dalam berarti permintaan yang gagal mengembalikan array kosong dan aliran terluar tetap mengalir. Memindahkan catchError ke pipe terluar akan menghentikan seluruh pencarian setelah kesalahan pertama, merusak setiap ketukan tombol berikutnya. Keempat operator perataan masing-masing menjawab pertanyaan konkurensi yang berbeda, dan memilih yang salah adalah salah satu sumber race condition paling umum di aplikasi Angular.

Memilih operator perataan

Gunakan switchMap untuk membatalkan permintaan sebelumnya dan hanya mempertahankan yang terbaru (pencarian typeahead). Gunakan mergeMap untuk menjalankan setiap permintaan secara bersamaan (unggahan paralel yang independen). Gunakan concatMap untuk mengantre permintaan dalam urutan yang ketat (penulisan berurutan yang tidak boleh tumpang tindih). Gunakan exhaustMap untuk mengabaikan input baru sampai yang sekarang selesai (memblokir tombol submit yang diklik ganda).

Memilih dengan tepat inilah yang mengubah fitur yang labil menjadi fitur yang dapat diprediksi. Modul operator RxJS SharpSkill mengasah perbedaan ini dengan pertanyaan berjenjang yang mereproduksi race condition persis yang dicegah oleh masing-masing operator.

Subject dan multicasting: BehaviorSubject, ReplaySubject, dan Subject

Sebuah Observable biasa bersifat unicast: setiap subscriber memicu eksekusi baru. Sebuah Subject adalah sekaligus Observable dan Observer, yang membuatnya multicast. Properti itu menjadikan Subject cara standar untuk membangun state store ringan atau event bus di dalam sebuah service.

Ketiga varian berbeda dalam apa yang mereka putar ulang kepada subscriber yang terlambat. Sebuah Subject biasa tidak memancarkan apa pun dari masa lalu, sehingga subscriber hanya melihat nilai yang tiba setelah ia berlangganan. Sebuah BehaviorSubject menyimpan nilai saat ini dan segera memutarnya ulang, itulah mengapa ia membutuhkan nilai awal. Sebuah ReplaySubject menyangga sejumlah pancaran masa lalu yang dapat dikonfigurasi dan memutar ulang semuanya.

cart.service.tstypescript
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class CartService {
  // BehaviorSubject holds the latest value and replays it to new subscribers
  private readonly itemsSubject = new BehaviorSubject<string[]>([]);

  // expose a read-only stream, never the writable Subject itself
  readonly items$ = this.itemsSubject.asObservable();

  add(item: string): void {
    const current = this.itemsSubject.value;    // synchronous access to the last value
    this.itemsSubject.next([...current, item]); // emit the next immutable state
  }
}

Dua kebiasaan penting di sini. Pertama, mengekspos asObservable() alih-alih Subject itu sendiri mencegah kode luar memanggil next() dan mengubah state melalui pintu belakang. Kedua, memancarkan referensi array baru alih-alih memutasinya di tempat menjaga change detection dan pemeriksaan kesetaraan tetap dapat diprediksi. Pola ini sudah ada sebelum Signals dan masih bekerja dengan baik untuk state bersama yang dapat diinjeksi yang dikonsumsi oleh banyak komponen tak terkait.

Meskipun demikian, sebuah BehaviorSubject yang digunakan murni untuk menyimpan satu nilai saat ini yang dibaca di template kini sering kali lebih baik diekspresikan sebagai Signal yang dapat ditulis, yang menghilangkan langganan dan formalitas asObservable(). Subject tetap menjadi pilihan yang lebih kuat ketika store juga perlu mentransformasi, men-debounce, atau menggabungkan pancarannya dengan aliran lain, karena operator tersebut tidak memiliki padanan Signal langsung. Memutuskan di antara keduanya per kasus penggunaan, alih-alih selalu menggunakan salah satunya di mana-mana, adalah tanda kefasihan yang dihargai pewawancara.

Interop RxJS dan Signals dengan toSignal dan toObservable

Paket interop menyediakan dua fungsi utama. toSignal mengonversi sebuah Observable menjadi Signal hanya-baca yang dapat dikonsumsi langsung oleh template, dan ia mengelola siklus hidup langganan secara otomatis. toObservable melakukan sebaliknya, mengubah sebuah Signal menjadi Observable sehingga operator RxJS dapat memproses perubahannya.

dashboard.component.tstypescript
import { Component, signal, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
import { switchMap } from 'rxjs/operators';

@Component({ selector: 'app-dashboard', template: `{{ user()?.name }}` })
export class DashboardComponent {
  private http = inject(HttpClient);

  // a Signal drives the query; toObservable turns each change into a stream event
  readonly userId = signal(1);
  private readonly user$ = toObservable(this.userId).pipe(
    switchMap((id) => this.http.get<{ name: string }>(`/api/users/${id}`))
  );

  // toSignal subscribes immediately and unsubscribes on destroy, no manual teardown
  readonly user = toSignal(this.user$, { initialValue: null });
}

Opsi initialValue layak diperhatikan. Tanpanya, toSignal mengembalikan Signal yang bertipe nilai atau undefined, karena belum ada yang dipancarkan. Meneruskan initialValue: null memberi template state awal yang terdefinisi dan tipe yang lebih bersih. Untuk sumber sinkron seperti BehaviorSubject, requireSync: true menjamin sebuah nilai tersedia pada pembacaan pertama dan menghapus undefined dari tipe sepenuhnya. Jembatan dua arah inilah yang memungkinkan basis kode mengadopsi Signals secara bertahap tanpa menulis ulang setiap pipeline Observable yang sudah ada. Modul wawancara Angular Signals pendamping membahas model reaktivitas secara mendalam, dan ikhtisar fitur Angular 18 Signals menelusuri bagaimana primitif ini berkembang.

Siap menguasai wawancara Angular Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Menjembatani Observables ke resource signal dengan rxResource

Angular 20 mempromosikan rxResource sebagai cara deklaratif untuk memuat data asinkron dari sebuah Observable ke dalam state berbasis signal. Ia melacak status loading, error, dan resolved sebagai signal, yang menghilangkan sebagian besar pembukuan langganan manual yang dulu dibutuhkan panggilan HTTP.

products.component.tstypescript
import { Component, signal, inject } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';

@Component({ selector: 'app-products', template: `` })
export class ProductsComponent {
  private http = inject(HttpClient);
  readonly category = signal('laptops');

  // Angular 20 renamed the fields: request -> params and loader -> stream
  readonly products = rxResource({
    params: () => ({ category: this.category() }),
    stream: ({ params }) =>
      this.http.get<Product[]>(`/api/products?category=${params.category}`),
  });
  // products.value(), products.isLoading() and products.error() are all signals
}

Penggantian nama dari request/loader di Angular 19 menjadi params/stream di Angular 20 adalah kesalahan yang sering terjadi, sehingga menyebutkan nama field saat ini menunjukkan pengetahuan yang mutakhir dalam wawancara. Ketika signal category berubah, rxResource membatalkan permintaan sebelumnya dan memulai aliran baru, memberikan semantik switchMap secara cuma-cuma. Referensi API rxResource mendokumentasikan siklus hidup status secara lengkap.

Menghindari kebocoran memori dengan takeUntilDestroyed

Setiap subscribe() manual yang hidup lebih lama dari komponennya membocorkan memori dan dapat memicu callback terhadap view yang sudah dihancurkan. Perbaikan historisnya adalah pola takeUntil(this.destroy$) dengan sebuah Subject yang diselesaikan di ngOnDestroy. Angular menggantikan boilerplate itu dengan takeUntilDestroyed, sebuah operator yang mengikat langganan ke konteks injeksi saat ini atau sebuah DestroyRef eksplisit.

live-feed.component.tstypescript
import { Component, inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';

@Component({ selector: 'app-live-feed', template: `` })
export class LiveFeedComponent {
  private destroyRef = inject(DestroyRef);

  constructor() {
    interval(1000)
      // completes the subscription automatically when the component is destroyed
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe((tick) => console.log('tick', tick));
  }
}

Dipanggil di dalam konstruktor atau inisialisasi field, takeUntilDestroyed() tidak membutuhkan argumen karena ia membaca DestroyRef yang ada di sekitar. Dipanggil di tempat lain, misalnya di dalam sebuah metode, ia membutuhkan DestroyRef eksplisit yang diteruskan, seperti ditunjukkan di atas. Mengutamakan toSignal dan rxResource menghindari langganan manual sepenuhnya, yang merupakan cara terbersih untuk mengelak dari kebocoran; ketika langganan mentah benar-benar dibutuhkan, takeUntilDestroyed adalah pengaman yang tepat.

Jebakan konteks injeksi

Memanggil takeUntilDestroyed() tanpa argumen di luar konteks injeksi melempar runtime error. Berlangganan di dalam event handler, sebuah setTimeout, atau sebuah callback RxJS berada di luar konteks itu, sehingga DestroyRef harus ditangkap lebih awal (biasanya sebagai field yang diinjeksi) dan diteruskan secara eksplisit. Melupakan hal ini adalah penyebab paling sering dari error "takeUntilDestroyed() can only be used within an injection context".

Pertanyaan wawancara RxJS Angular yang layak dilatih

Pewawancara cenderung berputar di topik-topik bersinyal tinggi yang sama. Mampu menjawab ini dengan ringkas membedakan kandidat yang telah menggunakan RxJS dari mereka yang hanya membacanya.

  • Hot versus cold Observables. Sebuah cold Observable memulai producer-nya per langganan, sehingga setiap subscriber mendapat eksekusi independen (permintaan HTTP baru). Sebuah hot Observable berbagi satu producer di seluruh subscriber, yang justru diciptakan oleh Subject dan share().
  • switchMap versus mergeMap versus concatMap versus exhaustMap. switchMap membatalkan Observable dalam sebelumnya (ideal untuk pencarian). mergeMap menjalankan semua Observable dalam secara bersamaan. concatMap mengantrenya dalam urutan. exhaustMap mengabaikan input baru selama satu sedang berjalan (ideal untuk mencegah submit formulir ganda).
  • Cara mencegah kebocoran memori. Utamakan toSignal, rxResource, atau pipe async, yang semuanya berhenti berlangganan secara otomatis; untuk langganan manual, gunakan takeUntilDestroyed.
  • Kapan mengonversi Observable menjadi Signal. Pilih toSignal ketika template membutuhkan nilai secara sinkron dan sumbernya asinkron, memungkinkan change detection membaca nilai terbaru tanpa pipe async.

Mengerjakan versi berjenjang dari pertanyaan-pertanyaan ini di bawah tekanan waktu adalah cara tercepat untuk menginternalisasikannya. Pusat teknologi Angular mengelompokkan modul RxJS, Signals, dan change detection yang memetakan langsung ke pertanyaan-pertanyaan ini.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Kesimpulan

RxJS dan Signals adalah mitra dalam stack Angular modern, bukan rival. Poin-poin utama yang layak dibawa ke proyek atau wawancara berikutnya:

  • Gunakan Signals untuk state komponen sinkron dan Observables untuk pipeline event asinkron yang membutuhkan operator.
  • Pilih switchMap untuk membatalkan pekerjaan usang, exhaustMap untuk memblokir duplikat, dan pertahankan catchError pada Observable dalam agar aliran terluar bertahan dari kegagalan.
  • Ekspos state Subject melalui asObservable() dan pancarkan nilai imutabel agar change detection tetap dapat diprediksi.
  • Jembatani kedua model dengan toSignal dan toObservable, dan teruskan initialValue atau requireSync untuk tipe yang bersih.
  • Muat data asinkron secara deklaratif dengan rxResource di Angular 20, dengan mengingat nama field params dan stream.
  • Jaga setiap langganan manual yang tersisa dengan takeUntilDestroyed untuk menghilangkan kebocoran.

Tag

#angular
#rxjs
#signals
#observables
#typescript
#deep-dive

Bagikan

Artikel terkait