RxJS (Reactive Extensions for JavaScript) is a library for composing asynchronous and event-based programs using observable sequences. Angular uses RxJS throughout — HTTP client, Router events, ReactiveForms, and animations. Mastering the core operators and patterns is essential for Angular development.

Key Points

  • Observable: lazy push-based stream of values — nothing happens until you subscribe()
  • Subject: both observable and observer — can emit values manually; hot (multicast) vs cold (unicast) observables
  • BehaviorSubject: Subject that holds a current value and emits it to new subscribers immediately
  • map: transform each value; filter: keep values matching predicate; tap: side effects without changing the stream
  • switchMap: cancel previous inner observable, start new one — ideal for HTTP calls triggered by input changes
  • mergeMap: run inner observables concurrently — parallel requests
  • concatMap: queue inner observables — sequential, ordered processing
  • combineLatest: emit when any source emits, combining latest from all — dashboard data from multiple endpoints
  • takeUntilDestroyed() (Angular 16+): auto-unsubscribe when component destroys — no more ngOnDestroy boilerplate
OperatorInner observablesUse case
switchMapCancel previous, start newSearch-as-you-type, navigation
mergeMapAll run concurrentlyParallel independent requests
concatMapQueue, run one at a timeSequential uploads, ordered jobs
exhaustMapIgnore new until current completesPrevent double-submit on button click

RxJS in Angular: switchMap search, combineLatest dashboard, BehaviorSubject state service, takeUntilDestroyed

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({ standalone: true, imports: [AsyncPipe, ReactiveFormsModule], ... })
export class SearchComponent {
  private destroyRef = inject(DestroyRef);
  private search = inject(SearchService);

  query = new FormControl('');

  // Search-as-you-type — switchMap cancels previous request
  results$ = this.query.valueChanges.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    filter(q => q!.length >= 2),
    switchMap(q => this.search.find(q).pipe(
      catchError(() => of([]))      // don't kill stream on error
    )),
    takeUntilDestroyed(this.destroyRef)  // auto-cleanup
  );
}

// combineLatest — dashboard with multiple data sources
@Component({ template: '...' })
export class DashboardComponent {
  vm$ = combineLatest({
    users:   inject(UserService).users$,
    orders:  inject(OrderService).recent$,
    metrics: inject(MetricsService).live$
  }).pipe(
    map(({ users, orders, metrics }) => ({ users, orders, metrics }))
  );
}

// BehaviorSubject for service state
@Injectable({ providedIn: 'root' })
export class CartService {
  private items$ = new BehaviorSubject<CartItem[]>([]);
  readonly cart$ = this.items$.asObservable();
  readonly count$ = this.cart$.pipe(map(items => items.length));

  addItem(item: CartItem) {
    this.items$.next([...this.items$.value, item]);
  }
}

// Template — async pipe handles subscribe/unsubscribe
// @if (vm$ | async; as vm) {
//   <app-user-list [users]="vm.users"/>
//   <app-orders [orders]="vm.orders"/>
// }

Real-World Example

switchMap is the solution to a race condition that plagues naive HTTP request handling: the user types fast and requests return out of order. Without switchMap, the last response shown might be for an earlier query. With switchMap, each new keystroke cancels the previous HTTP request (via AbortController under the hood) — only the latest request's response is shown.