RxJS ile reaktif programlama: Angular uygulamasında veri akışlarını kurmak
Beceri rehberi

RxJS ile reaktif programlama: Angular uygulamasında veri akışlarını kurmak

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 5 dk okuma

Angular'da Observable, pipe ve operatörlerle arama kutusu, HTTP istekleri ve abonelik yönetimi örneklerle: switchMap, async pipe ve sızıntı önleme.

Reaktif yaklaşım neyi çözer?

Bir Angular uygulamasında veri tek seferlik gelmez: kullanıcı yazar, tıklar, sayfa değiştirir, sunucu yanıt verir, bir form değeri güncellenir. Bu olayların her biri zaman içinde akan bir değerdir. RxJS bu akışları Observable adlı bir nesneyle temsil eder ve onları birleştirmek, dönüştürmek, süzmek için ortak bir araç seti sunar.

Neden doğrudan geri çağrı (callback) ya da Promise kullanmıyoruz? Promise tek bir değer üretir ve iptal edilemez; Observable ise birden çok değer üretebilir, iptal edilebilir ve operatörlerle zincirlenebilir. Angular'ın kendi birimleri de bu modeli kullanır: HttpClient yanıtı bir Observable olarak döner, Router olayları, reaktif formların değer ve durum değişimleri de akış olarak sunulur.

Temel yapı: Observable, subscribe ve pipe

Bir Observable, kendisine abone olunana kadar bir şey yapmaz. Abone olmak için subscribe çağrılır; aboneye gelen değer, hata ve tamamlanma bilgisi iletilebilir. Operatörler ise pipe içinde zincirlenir ve her biri yeni bir Observable döndürür; kaynak akış değişmez.

Küçük bir örnek:

fiyatlarAkisi = this.http.get<Product[]>('/api/products').pipe( map(liste => liste.filter(u => u.stokta)), catchError(() => of([])) );

Burada get isteği bir akış üretir, map ile yalnızca stoktaki ürünler seçilir, catchError ile hata durumunda boş liste döndürülür. Bu akışa henüz abone olunmadığı için istek de atılmamıştır. Observable değişkenlerini adlarından ayırt edilir kılmak için bir adlandırma düzeni (örneğin sonuna Akisi eklemek) kullanılabilir.

Arama kutusu örneği: debounceTime, distinctUntilChanged, switchMap

Reaktif yaklaşımın en öğretici örneği, kullanıcı yazdıkça sonuç getiren arama kutusudur. Sorun şudur: her tuşa basışta istek atmak sunucuyu yorar, yanıtlar ters sırayla gelirse ekranda eski sonuç kalır. Çözüm birkaç operatörün birleşimidir:

arama = new FormControl('');

sonuclarAkisi = this.arama.valueChanges.pipe( debounceTime(bekleme), distinctUntilChanged(), switchMap(terim => this.servis.ara(terim)), catchError(() => of([])) );

debounceTime, kullanıcı yazmayı kestikten sonra belirli bir süre bekler. distinctUntilChanged, aynı değer art arda gelirse akışı durdurur. switchMap ise yeni bir değer geldiğinde önceki devam eden isteği iptal eder ve yenisine geçer; böylece eski yanıtın yenisini ezmesi önlenir. Bekleme süresi sabit bir değerdir ve ekranın hissine göre ayarlanır.

Burada operatör seçimi önemlidir. switchMap arama için doğrudur; ama bir kaydetme düğmesinde istekleri sırayla tamamlamak istiyorsanız concatMap, paralel çalıştırmak istiyorsanız mergeMap, çift tıklamayı yok saymak istiyorsanız exhaustMap daha uygun olur.

Şablonda tüketmek: async pipe

Akışı bileşende elle abone olup bir alana yazmak yerine, şablonda async pipe ile tüketmek hem daha kısa hem daha güvenlidir. async pipe, bileşen yok olunca aboneliği kendisi kapatır.

Şablon örneği: *ngFor ya da @for ile sonuclarAkisi | async ifadesi üzerinden liste çizilir. Aynı akışı şablonda birden çok yerde async ile okumak her seferinde ayrı abonelik yaratabilir; bu durumda akışı bir kez okuyup bir değişkene atamak (örneğin @if ile) ya da akışı paylaşılabilir hale getirmek gerekir.

Yükleniyor ve hata durumlarını da akışın parçası yapabilirsiniz: yükleme bilgisini ayrı bir akıştan türetmek ya da catchError içinde bir hata durumu nesnesi döndürmek, şablonu sade tutar. Böylece bileşenin sınıfında durum bayrağı (isLoading = true, sonra false) yönetme zahmeti azalır.

Abonelik sızıntısını önlemek

Elle subscribe ettiğiniz ve kendiliğinden tamamlanmayan akışlar (form değer değişimleri, Router olayları, sürekli açık bir bağlantı) bileşen yok edildikten sonra da çalışmaya devam eder. Bu bellek sızıntısıdır ve ekranda hayalet güncellemelere yol açabilir. Önleme yolları:

  • Mümkünse async pipe kullanın; abonelik yönetimini Angular'a bırakın.
  • Elle abone oluyorsanız takeUntilDestroyed operatörünü pipe içinde kullanın; bileşen yok edildiğinde akış otomatik biter.
  • Kısa ömürlü akışlar için take ya da first gibi operatörlerle tek değerde bitirin.
  • Abonelik nesnesini saklayıp ngOnDestroy içinde unsubscribe çağırmak eski ama hâlâ geçerli bir yöntemdir.

HttpClient istekleri tek değer üretip tamamlandığı için genelde sızıntı yaratmaz; riski taşıyanlar kendiliğinden bitmeyen akışlardır. Bir bileşende kaç abonelik açtığınızı ve hangisinin kim tarafından kapatıldığını sayabiliyor olmak iyi bir kontrol sorusudur.

Servis katmanında paylaşılan durum ve sinyallerle ilişki

Birden çok bileşenin aynı veriyi görmesi gerektiğinde, akışı bir serviste tutmak yaygın bir kalıptır. BehaviorSubject, güncel değeri hatırlayan bir Subject'tir; yeni abone hemen son değeri alır. Servis içeride Subject'i yönetir, dışarıya yalnızca asObservable ile salt okunur akış verir; böylece her bileşenin akışa doğrudan yazması engellenir.

Angular'ın sinyal sistemi ile RxJS birlikte kullanılır. Resmî belgede toSignal, bir Observable'ın son değerini izleyen salt okunur bir sinyal oluşturur; toObservable ise bir sinyali akışa çevirir. Genel yaklaşım: zaman içinde olay gibi akan, iptal ve zamanlama gerektiren işleri (arama, HTTP, olay akışları) RxJS ile; şablonda okunan senkron durumu sinyalle yönetmek.

Sık hatalar ve Mindset bağlamı

Sık yapılan hatalar şunlardır: içeride subscribe çağrısının içinde yeniden subscribe etmek (bunun yerine switchMap kullanın), her şeyi tek bir uzun zincirde toplamak, hata yönetimini unutmak ve bir hata geldiğinde akışın ölmesine izin vermek, paylaşılması gereken akışta ayrı abonelikler yüzünden isteği tekrarlamak.

Mindset'in Angular ile Kurumsal Web Uygulamaları programında RxJS ile Reaktif Veri Akışları ayrı bir modül olarak yer alır; mimari, bağımlılık enjeksiyonu, formlar, yönlendirme, durum yönetimi ve performans konularıyla birlikte işlenir. Bu program, kurumsal bir arayüz geliştiren arayüz tarafında çalışan geliştiriciler ve Angular'a yeni geçen ekipler için uygundur; çünkü akış yönetimi kurumsal uygulamalarda en sık hata çıkan bölümlerden biridir.

İlgili Mindset programı

Angular ile Kurumsal Web Uygulamaları

Sık sorulanlar

Sık sorulan sorular

RxJS öğrenmeden Angular yazılabilir mi?

Temel işler yazılabilir, ancak HTTP, yönlendirme ve reaktif formlar Observable kullandığı için akışları anlamadan ilerlemek zorlaşır. Önce subscribe, pipe, map, filter ve switchMap gibi birkaç operatörü iyi öğrenmek yeterlidir. Geri kalanı ihtiyaç doğdukça eklenir.

Observable ile Promise arasındaki fark nedir?

Promise tek bir değer üretir, oluşturulduğu anda çalışır ve iptal edilemez. Observable birden çok değer üretebilir, abone olunana kadar çalışmaz ve abonelik kapatılarak iptal edilebilir. Bu fark, arama kutusu gibi eskinin iptal edilmesi gereken senaryolarda belirleyicidir.

switchMap, mergeMap ve concatMap arasında nasıl seçim yapılır?

Yalnızca en son isteğin sonucu önemliyse switchMap, tüm istekler paralel çalışsın istiyorsanız mergeMap, sıra korunmalıysa concatMap kullanılır. Düğmeye art arda basmayı yok saymak için exhaustMap vardır. Seçim, iş kuralınıza göre yapılır; performans için değil davranış için seçilir.

Her Observable için unsubscribe etmek gerekir mi?

Hayır. Tek değer üretip tamamlanan akışlar (örneğin çoğu HTTP isteği) kendiliğinden kapanır. Kendiliğinden bitmeyen akışlarda, örneğin form değer değişimlerinde, abonelik kapatılmalıdır. En güvenli yol async pipe ya da takeUntilDestroyed kullanmaktır.

Sinyaller RxJS yerine mi geçiyor?

Hayır, tamamlayıcıdırlar. Sinyaller şablonda okunan senkron durum için sade bir model sunar; RxJS ise zamanlama, iptal ve olay akışları için güçlüdür. Angular, bu yaklaşım arasında geçiş için toSignal ve toObservable sağlar. Uygulamanın ihtiyacına göre ikisi birlikte kullanılır.

Angular'da RxJS ile Veri Akışı Yönetimi