Angular RxJS: The Essential Cheat Sheet

Introduction to RxJS in Angular

RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using Observables, making it easier to compose asynchronous or callback-based code. In Angular applications, RxJS is fundamental for handling events, HTTP requests, and state management. It provides powerful tools to work with asynchronous data streams in a declarative, composable way that simplifies complex operations like filtering, transforming, and combining data.

Core Concepts of RxJS

Observable

  • Definition: Represents a stream of data that can be observed over time
  • Creation: Used to wrap event sources like user inputs, HTTP responses, or timers
  • Lifecycle: Can emit multiple values, error events, and a completion event

Observer

  • Definition: Consumer of values delivered by an Observable
  • Components: Contains callbacks for next(), error(), and complete() notifications
  • Usage: Subscribes to Observables to receive emitted values

Subscription

  • Definition: Represents the execution of an Observable
  • Purpose: Allows cancellation of ongoing Observable executions
  • Important: Must be unsubscribed to prevent memory leaks

Operators

  • Definition: Pure functions that transform, filter, or combine Observables
  • Categories: Creation, Transformation, Filtering, Combination, Utility, etc.
  • Chaining: Can be piped together for complex data manipulations

Subject

  • Definition: Special type of Observable that allows values to be multicasted to many Observers
  • Types: Subject, BehaviorSubject, ReplaySubject, AsyncSubject

Creating Observables

MethodDescriptionExample
new Observable()Create custom Observablenew Observable(observer => { observer.next(1); })
of()Create Observable from valuesof(1, 2, 3)
from()Convert array, promise, or iterable to Observablefrom([1, 2, 3])
fromEvent()Convert DOM events to ObservablefromEvent(button, 'click')
interval()Emit sequential numbers at specified intervalinterval(1000)
timer()Like interval but with initial delaytimer(3000, 1000)
ajax()Create Observable for AJAX requestsajax.getJSON('/api/data')

Essential Operators by Category

Transformation Operators

  • map(fn): Transform each value with provided function
  • pluck(key): Extract a property from each emitted value
  • switchMap(fn): Map to Observable, completing previous inner Observables
  • mergeMap(fn): Map to Observable, maintaining all inner Observables
  • concatMap(fn): Map to Observable, queuing inner Observables
  • exhaustMap(fn): Map to Observable, ignoring new inner Observables until current completes
  • scan(fn, seed): Apply accumulator function like reduce, but emits each result

Filtering Operators

  • filter(predicate): Emit values passing a predicate function
  • take(n): Take first n values then complete
  • takeUntil(notifier$): Take values until notifier Observable emits
  • skip(n): Skip first n values
  • distinctUntilChanged(): Only emit when current value differs from previous
  • debounceTime(ms): Emit value after specified time has passed without another source emission
  • throttleTime(ms): Emit first value, then ignore for specified duration

Combination Operators

  • combineLatest([obs1$, obs2$]): Combine latest values from multiple Observables
  • merge(obs1$, obs2$): Merge multiple Observables, emitting values as they arrive
  • concat(obs1$, obs2$): Concat Observables, subscribing to next after previous completes
  • zip(obs1$, obs2$): Combine emissions by index from multiple Observables
  • withLatestFrom(obs2$): Combine with latest value from another Observable

Error Handling Operators

  • catchError(fn): Handle errors, returning new Observable or throwing
  • retry(count): Retry failed Observable sequence specified number of times
  • retryWhen(notifier): Retry when notifier Observable emits

Common RxJS Patterns in Angular

HTTP Request Handling

this.http.get<User[]>('/api/users').pipe(
  map(users => users.filter(user => user.active)),
  catchError(error => {
    console.error('Error fetching users', error);
    return of([]);  // Return empty array on error
  })
).subscribe(
  users => this.users = users
);

Component Communication

// Service
private messageSource = new BehaviorSubject<string>('default message');
currentMessage$ = this.messageSource.asObservable();

changeMessage(message: string): void {
  this.messageSource.next(message);
}

// Component
this.dataService.currentMessage$.subscribe(message => {
  this.message = message;
});

Form Value Changes

this.form.valueChanges.pipe(
  debounceTime(500),
  distinctUntilChanged(),
  tap(value => console.log('Form value changed:', value))
).subscribe();

Search with Type-ahead

this.searchControl.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(term => this.searchService.search(term))
).subscribe(results => {
  this.searchResults = results;
});

Combining Multiple Data Sources

combineLatest([
  this.userService.getUser(),
  this.productService.getProducts()
]).pipe(
  map(([user, products]) => {
    return {
      user,
      recommendedProducts: products.filter(p => p.category === user.preferences)
    };
  })
).subscribe(data => {
  this.userData = data;
});

Handling Subscriptions in Angular

Manual Subscription Management

ngOnInit() {
  this.subscription = this.data$.subscribe(data => {
    // Handle data
  });
}

ngOnDestroy() {
  if (this.subscription) {
    this.subscription.unsubscribe();
  }
}

Subscription Collection

private subscriptions = new Subscription();

ngOnInit() {
  this.subscriptions.add(
    this.data1$.subscribe(data => {
      // Handle data 1
    })
  );
  
  this.subscriptions.add(
    this.data2$.subscribe(data => {
      // Handle data 2
    })
  );
}

ngOnDestroy() {
  this.subscriptions.unsubscribe();
}

Using Async Pipe

<!-- In template -->
<div *ngIf="data$ | async as data">
  {{ data.name }}
</div>

Subject Types Comparison

TypeBehaviorUse Case
SubjectBasic implementation; only subscribers receive values emitted after subscriptionEvent buses, simple pub/sub scenarios
BehaviorSubjectRequires initial value; new subscribers get latest value upon subscriptionComponent states, form values
ReplaySubjectBuffers specified number of values; new subscribers get buffered valuesUser action history, caching last N values
AsyncSubjectOnly emits final value to subscribers upon completionAPI responses where only final value matters

Common Challenges and Solutions

Memory Leaks

  • Challenge: Unmanaged subscriptions causing memory leaks
  • Solution:
    • Always unsubscribe in ngOnDestroy
    • Use the async pipe when possible
    • Use takeUntil with a destroy subject
private destroy$ = new Subject<void>();

ngOnInit() {
  this.data$.pipe(
    takeUntil(this.destroy$)
  ).subscribe(data => {
    // Handle data
  });
}

ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

Race Conditions

  • Challenge: Multiple overlapping HTTP requests
  • Solution: Use switchMap to cancel previous requests

Back Pressure

  • Challenge: Observable emitting faster than consumer can process
  • Solution: Use throttling/debouncing operators or buffer operators

Cold vs Hot Observables

  • Challenge: Unexpected behavior due to not understanding Observable nature
  • Solution: Use share() or shareReplay() to convert cold to hot when needed

Best Practices for RxJS in Angular

  1. Use Appropriate Flattening Operators

    • switchMap for requests where only latest matters (searches)
    • concatMap for sequential operations where order matters
    • mergeMap for parallel operations with no ordering requirement
    • exhaustMap for operations that should prevent new ones (form submits)
  2. Centralize State Management

    • Use BehaviorSubject in services for application state
    • Expose only observables (using .asObservable())
  3. Leverage Pipeable Operators

    • Create custom operators for reusable logic
    • Chain operators with pipe() method
  4. Error Handling Strategy

    • Always add proper error handling with catchError
    • Consider retry strategies for transient failures
  5. Performance Considerations

    • Use shareReplay() for expensive operations that need to be shared
    • Unsubscribe from long-lived Observables
    • Be mindful of operator choice (e.g., avoid unnecessary distinctUntilChanged)
  6. Debugging

    • Use tap() operator for debugging without affecting the stream
    • Consider using RxJS debugging tools like rxjs-spy

Resources for Further Learning

  • Official Documentation: RxJS Documentation
  • Interactive Learning: RxJS Marbles
  • Angular Documentation: Angular RxJS Guide
  • Books:
    • “RxJS in Action” by Paul P. Daniels and Luis Atencio
    • “Reactive Programming with RxJS” by Sergi Mansilla
  • Courses:
    • “RxJS Fundamentals” on Pluralsight
    • “RxJS: Reactive Programming” on egghead.io
  • Tools:
    • RxJS Devtools for Chrome
    • rxjs-spy for debugging

This cheatsheet provides a comprehensive overview of RxJS in Angular, from basic concepts to advanced patterns. Use it as a quick reference when implementing reactive programming patterns in your Angular applications.

Scroll to Top