Angular'da RxJS 2026: Operatörler, Subject'ler ve Signals Interop

Angular'da RxJS 2026: üretimde kullanılan operatörleri, Subject'leri ve Signals interop kalıplarını öğrenin, ayrıca en sık karşılaşılan mülakat sorularını.

Angular'da RxJS 2026 operatörler, Subject'ler ve Signals interop

RxJS, Angular'da 2026 yılında da asenkron programlamanın belkemiği olmayı sürdürüyor; Signals, Angular bileşenlerinin senkron durumu izleme biçimini yeniden şekillendirse bile. Ne zaman bir Observable'a başvurulacağını, ne zaman bir Signal'in daha uygun olduğunu ve ikisinin nasıl birlikte çalıştığını bilmek artık her Angular geliştiricisi için temel bir beceri. Bu derinlemesine inceleme, mülakatta kendinden emin bir yanıtı tıkanıp kalan bir yanıttan ayıran operatörleri, Subject'leri ve interop kalıplarını adım adım ele alıyor.

Tek cümlede Signals ile Observables

Signals, her zaman güncel bir değere sahip olan senkron durumu modeller; Observables ise zaman içinde akan asenkron olay akışlarını modeller. Angular 20 her ikisini de korur ve @angular/core/rxjs-interop paketi ikisi arasındaki resmi köprüdür.

Angular Signals'in yanında RxJS neden hâlâ önemli

Signals'in tanıtılması birçok ekibi RxJS'in devrinin kapanıp kapanmadığını sormaya yöneltti. Üç ana sürüm sonrasında yanıt: hayır. Signals, şu anda bir değeri olan durumu temsil etmekte üstündür: bir sayaç, bir form alanı, seçili bir sekme. Observables ise zaman içinde gelen ve dönüştürme, iptal etme ya da birleştirme gerektirebilen olayları modellemekte üstündür: HTTP yanıtları, WebSocket mesajları, klavye girişi, router olayları ve zamanlayıcılar.

Angular'ın kendi API'leri bu ayrımı görünür kılar. HttpClient hâlâ Observables döndürür. Reaktif formlar valueChanges ve statusChanges'i Observables olarak sunar. Router olayları bir Observable üzerinden akar. Aynı kanaldan birden fazla asenkron olayın aktığı her yerde RxJS operatörleri hâlâ mevcut en anlatımlı araçtır. Resmi Angular reaktiflik kılavuzu iki modeli rakip değil tamamlayıcı olarak ele alır ve RxJS belgeleri her önemsiz olmayan veri akışı için gerekli bir başvuru kaynağı olmayı sürdürür.

Code review'da geçerliliğini koruyan pratik kural: şablonlarda okunan yerel bileşen durumu için Signals, operatör gerektiren asenkron ardışık düzenler için Observables kullanın. Interop katmanı ikisini birbirine bağlar, böylece hiçbir model bir duvara dönüşmez.

Birleştirme operatörleri bu noktayı pekiştirir. Rota parametrelerini bir kullanıcının kaydettiği filtreler ve canlı bir fiyat akışıyla birleştirmek gibi birkaç asenkron kaynağı koordine etmek, tam olarak combineLatest, withLatestFrom ve forkJoin'in var olma amacıdır. Signals başka sinyallerden hesaplanmış değerler türetebilir, ancak "yalnızca her üç kaynak da en az bir değer ürettikten sonra yay" gibi zamana dayalı bir koordinasyonu ifade edemez. Zaman içinde bu bildirimsel bileşim, RxJS'in araç kutusunda kalmasının kalıcı nedenidir.

Her Angular geliştiricisinin dayandığı temel RxJS operatörleri

Operatörler, bir Observable alıp yenisini döndüren saf fonksiyonlardır. Klasik mülakat senaryosu yazarken aramadır, çünkü aynı anda dört operatörü çalıştırır: girişi kısıtlar, yinelenenleri atlar, eskimiş istekleri iptal eder ve hatalardan toparlanır.

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
      )
    )
  );
}

Mülakatçıların irdelediği ince ayrıntı, catchError onun içine yerleştirilmişken neden switchMap'in dış düzeyde yer aldığıdır. catchError'ı iç HTTP Observable'ına koymak, başarısız bir isteğin boş bir dizi döndürmesi ve dış akışın akmayı sürdürmesi anlamına gelir. catchError'ı dış pipe'a taşımak, ilk hatadan sonra tüm aramayı sonlandırır ve sonraki her tuş vuruşunu bozar. Dört düzleştirme operatörünün her biri farklı bir eşzamanlılık sorusuna yanıt verir ve yanlışını seçmek Angular uygulamalarında yarış koşullarının en yaygın kaynaklarından biridir.

Bir düzleştirme operatörü seçmek

Önceki iç isteği iptal edip yalnızca en sonuncuyu tutmak için switchMap kullanın (typeahead arama). Her iç isteği eşzamanlı çalıştırmak için mergeMap kullanın (bağımsız paralel yüklemeler). İstekleri kesin bir sırayla kuyruğa almak için concatMap kullanın (üst üste binmemesi gereken sıralı yazmalar). Geçerli olan bitene kadar yeni girişleri yok saymak için exhaustMap kullanın (çift tıklanan bir gönder düğmesini engellemek).

Bu seçimi doğru yapmak, kararsız bir özelliği öngörülebilir bir özelliğe dönüştürür. SharpSkill RxJS operatörleri modülü, her operatörün önlediği yarış koşullarını birebir yeniden üreten notlandırılmış sorularla bu ayrımları çalıştırır.

Subject'ler ve multicasting: BehaviorSubject, ReplaySubject ve Subject

Düz bir Observable unicast'tir: her abone taze bir yürütme tetikler. Bir Subject hem bir Observable hem de bir Observer'dır, bu da onu multicast yapar. Bu özellik, Subject'leri bir servis içinde hafif bir durum deposu ya da olay veri yolu kurmanın standart yolu hâline getirir.

Üç varyant, geç abonelere neyi yeniden oynattıklarıyla ayrışır. Düz bir Subject geçmişten hiçbir şey yaymaz, bu yüzden bir abone yalnızca abone olduktan sonra gelen değerleri görür. Bir BehaviorSubject güncel değeri saklar ve onu hemen yeniden oynatır; başlangıç değerine ihtiyaç duymasının nedeni budur. Bir ReplaySubject, yapılandırılabilir sayıda geçmiş yayını arabelleğe alır ve hepsini yeniden oynatır.

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
  }
}

Burada iki alışkanlık önemlidir. İlki, Subject yerine asObservable() sunmak, dış kodun next() çağırıp durumu arka kapıdan değiştirmesini engeller. İkincisi, yerinde değiştirmek yerine yeni bir dizi referansı yaymak, değişiklik algılamayı ve eşitlik kontrollerini öngörülebilir tutar. Bu kalıp Signals'ten öncedir ve birbiriyle ilgisiz birçok bileşenin tükettiği paylaşımlı, enjekte edilebilir durum için hâlâ iyi çalışır.

Bununla birlikte, yalnızca şablonlarda okunan tek bir güncel değeri tutmak için kullanılan bir BehaviorSubject artık çoğu zaman yazılabilir bir Signal olarak daha iyi ifade edilir; bu, aboneliği ve asObservable() seremonisini ortadan kaldırır. Depo aynı zamanda yayınlarını dönüştürmesi, kısıtlaması ya da başka akışlarla birleştirmesi gerektiğinde Subject daha güçlü seçim olmayı sürdürür, çünkü bu operatörlerin doğrudan bir Signal karşılığı yoktur. Her yerde birini varsayılan yapmak yerine kullanım durumuna göre ikisi arasında karar vermek, mülakatçıların ödüllendirdiği bir akıcılık işaretidir.

toSignal ve toObservable ile RxJS signals interop

Interop paketi iki başrol fonksiyonuyla gelir. toSignal, bir Observable'ı bir şablonun doğrudan tüketebileceği salt okunur bir Signal'e dönüştürür ve abonelik yaşam döngüsünü otomatik yönetir. toObservable bunun tersini yapar; bir Signal'i bir Observable'a çevirerek RxJS operatörlerinin onun değişikliklerini işleyebilmesini sağlar.

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 });
}

initialValue seçeneği dikkati hak eder. O olmadan toSignal, henüz hiçbir şey yayılmadığı için değer ya da undefined olarak tiplenmiş bir Signal döndürür. initialValue: null geçmek, şablona tanımlı bir başlangıç durumu ve daha temiz bir tip verir. Bir BehaviorSubject gibi senkron kaynaklar için requireSync: true, ilk okumada bir değerin hazır olmasını garanti eder ve undefined'ı tipten tümüyle çıkarır. Bu iki yönlü köprü, bir kod tabanının mevcut her Observable ardışık düzenini yeniden yazmadan Signals'i kademeli olarak benimsemesini sağlar. Beraberindeki Angular Signals mülakat modülü reaktiflik modelini derinlemesine ele alır ve Angular 18 Signals özelliklerine genel bakış bu ilkellerin nasıl evrildiğini izler.

Angular mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

rxResource ile Observable'ları signal kaynaklarına köprülemek

Angular 20, bir Observable'dan asenkron veriyi signal tabanlı duruma yüklemenin bildirimsel yolu olarak rxResource'u öne çıkarır. Yükleme, hata ve çözülmüş durumu signals olarak izler; bu da HTTP çağrılarının eskiden gerektirdiği manuel abonelik defter tutmanın çoğunu ortadan kaldırır.

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
}

Angular 19'daki request/loader'dan Angular 20'deki params/stream'e yeniden adlandırma sık bir tökezleme noktasıdır, bu yüzden güncel alan adlarını anmak bir mülakatta güncel bilgiye işaret eder. category signal'i değiştiğinde rxResource önceki isteği iptal eder ve yeni bir akış başlatır; bu da switchMap semantiğini bedavaya verir. rxResource API başvurusu tam durum yaşam döngüsünü belgeler.

takeUntilDestroyed ile bellek sızıntılarından kaçınmak

Bileşeninden daha uzun yaşayan her manuel subscribe(), bellek sızdırır ve yok edilmiş bir görünüme karşı geri çağrıları tetikleyebilir. Tarihsel çözüm, ngOnDestroy'da tamamlanan bir Subject ile takeUntil(this.destroy$) kalıbıydı. Angular bu tekrar eden şablonu, aboneliği geçerli enjeksiyon bağlamına ya da açık bir DestroyRef'e bağlayan takeUntilDestroyed operatörüyle değiştirdi.

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));
  }
}

Bir kurucu ya da alan başlatıcısı içinde çağrıldığında takeUntilDestroyed(), ortamdaki DestroyRef'i okuduğu için argümana gerek duymaz. Başka bir yerde, örneğin bir metot içinde çağrıldığında, yukarıda gösterildiği gibi açıkça geçirilen bir DestroyRef gerektirir. toSignal ve rxResource'u tercih etmek manuel abonelikleri tümüyle ortadan kaldırır; bu, sızıntılardan kaçınmanın en temiz yoludur. Ham bir aboneliğe gerçekten ihtiyaç duyulduğunda takeUntilDestroyed doğru korumadır.

Enjeksiyon bağlamı tuzağı

takeUntilDestroyed()'ı bir enjeksiyon bağlamının dışında argümansız çağırmak bir çalışma zamanı hatası fırlatır. Bir olay işleyicisi, bir setTimeout ya da bir RxJS geri çağrısı içinde abone olmak bu bağlamın dışındadır, bu yüzden DestroyRef daha önce (genellikle enjekte edilmiş bir alan olarak) yakalanıp açıkça geçirilmelidir. Bunu unutmak, "takeUntilDestroyed() can only be used within an injection context" hatasının en sık nedenidir.

Tekrar edilmeye değer Angular RxJS mülakat soruları

Mülakatçılar genellikle aynı yüksek sinyalli konuların etrafında döner. Bunları net biçimde yanıtlayabilmek, RxJS'i kullanmış adayları yalnızca onun hakkında okumuş olanlardan ayırır.

  • Hot ve cold Observables. Cold Observable üreticisini her abonelikte başlatır, bu yüzden her abone bağımsız bir yürütme alır (taze bir HTTP isteği). Hot Observable tek bir üreticiyi aboneler arasında paylaşır; Subject'ler ve share()'in yarattığı şey budur.
  • switchMap, mergeMap, concatMap ve exhaustMap. switchMap önceki iç Observable'ı iptal eder (arama için ideal). mergeMap tüm iç Observable'ları eşzamanlı çalıştırır. concatMap onları sırayla kuyruğa alır. exhaustMap biri çalışırken yeni girişleri yok sayar (çift form gönderimini önlemek için ideal).
  • Bellek sızıntıları nasıl önlenir. Tümü otomatik abonelikten çıkan toSignal, rxResource ya da async pipe'ı tercih edin; manuel abonelikler için takeUntilDestroyed kullanın.
  • Bir Observable ne zaman Signal'e dönüştürülür. Bir şablonun bir değere senkron ihtiyaç duyduğu ve kaynağın asenkron olduğu durumlarda toSignal'a başvurun; böylece değişiklik algılama en son değeri bir async pipe olmadan okur.

Bunların notlandırılmış sürümlerini zaman baskısı altında çalışmak, onları içselleştirmenin en hızlı yoludur. Angular teknoloji merkezi bu sorulara doğrudan karşılık gelen RxJS, Signals ve değişiklik algılama modüllerini bir araya getirir.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Sonuç

RxJS ve Signals modern Angular yığınında rakip değil, ortaktır. Bir sonraki projeye ya da mülakata taşımaya değer çıkarımlar:

  • Senkron bileşen durumu için Signals, operatör gerektiren asenkron olay ardışık düzenleri için Observables kullanın.
  • Eskimiş işi iptal etmek için switchMap'e, yinelenenleri engellemek için exhaustMap'e başvurun ve dış akışın hataları atlatması için catchError'ı iç Observable'da tutun.
  • Subject durumunu asObservable() üzerinden sunun ve değişiklik algılamayı öngörülebilir tutmak için değişmez değerler yayın.
  • İki modeli toSignal ve toObservable ile köprüleyin ve temiz tipler için initialValue ya da requireSync geçirin.
  • Angular 20'de asenkron veriyi rxResource ile bildirimsel olarak yükleyin ve params ile stream alan adlarını unutmayın.
  • Sızıntıları ortadan kaldırmak için kalan her manuel aboneliği takeUntilDestroyed ile koruyun.

Etiketler

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

Paylaş

İlgili makaleler