avatar

Le Do Nghiem

Software Engineer

  • About me
  • Books
  • Snippets
  • Blog

© 2026 Le Do Nghiem. All rights reserved.

Contact |

Back to Blog

Mastering RxJS in Angular: Reactive Programming Patterns

avatar
Le Do NghiemSoftware Engineer
2025-12-19 4 min read

Introduction

RxJS (Reactive Extensions for JavaScript) is at the heart of Angular's reactivity model. Understanding RxJS is crucial for building efficient, maintainable Angular applications. In this post, we'll explore essential RxJS patterns and how to use them effectively in Angular.

Understanding Observables and Observers

At its core, RxJS is built on Observables—streams of data that can be observed over time.

import { Observable } from 'rxjs';

// Creating a simple observable
const observable = new Observable<number>(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});

// Subscribing to the observable
observable.subscribe({
  next: value => console.log(value),
  error: err => console.error(err),
  complete: () => console.log('Complete')
});

Common RxJS Operators in Angular

map, filter, and tap

These operators are essential for transforming and inspecting data streams.

import { map, filter, tap } from 'rxjs/operators';

this.userService.getUsers()
  .pipe(
    tap(users => console.log('Received users:', users)), // Side effect
    map(users => users.filter(user => user.active)),     // Transform
    filter(activeUsers => activeUsers.length > 0)         // Filter
  )
  .subscribe(activeUsers => {
    this.activeUsers = activeUsers;
  });

switchMap for Cancelling Previous Requests

switchMap is perfect for search functionality where you want to cancel previous requests.

import { Subject } from 'rxjs';
import { switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators';

export class SearchComponent {
  private searchSubject = new Subject<string>();
  
  ngOnInit() {
    this.searchSubject.pipe(
      debounceTime(300),              // Wait 300ms after user stops typing
      distinctUntilChanged(),         // Only emit if value changed
      switchMap(query =>              // Cancel previous, start new
        this.userService.searchUsers(query)
      )
    ).subscribe(results => {
      this.searchResults = results;
    });
  }
  
  onSearch(query: string) {
    this.searchSubject.next(query);
  }
}

combineLatest for Multiple Data Sources

Use combineLatest when you need data from multiple observables.

import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';

ngOnInit() {
  combineLatest([
    this.userService.getCurrentUser(),
    this.productService.getProducts(),
    this.cartService.getCart()
  ]).pipe(
    map(([user, products, cart]) => ({
      user,
      products,
      cart,
      canCheckout: cart.items.length > 0 && user.isAuthenticated
    }))
  ).subscribe(data => {
    this.dashboardData = data;
  });
}

Managing Subscriptions

Using async Pipe (Recommended)

The async pipe automatically handles subscription and unsubscription.

// Component
export class UserListComponent {
  users$ = this.userService.getUsers();
  
  constructor(private userService: UserService) {}
}
<!-- Template -->
<div *ngFor="let user of users$ | async">
  {{ user.name }}
</div>

Manual Subscription Management

When you need manual control, always unsubscribe to prevent memory leaks.

import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

export class UserComponent implements OnDestroy {
  private subscription = new Subscription();
  
  ngOnInit() {
    const userSub = this.userService.getUser().subscribe(user => {
      this.user = user;
    });
    
    this.subscription.add(userSub);
  }
  
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Error Handling

Proper error handling is crucial for robust applications.

import { catchError, retry } from 'rxjs/operators';
import { of } from 'rxjs';

this.userService.getUsers()
  .pipe(
    retry(3),                    // Retry up to 3 times
    catchError(error => {
      console.error('Error fetching users:', error);
      return of([]);            // Return empty array on error
    })
  )
  .subscribe(users => {
    this.users = users;
  });

Subjects for Component Communication

Subjects are useful for component-to-component communication.

import { Subject } from 'rxjs';

@Injectable()
export class NotificationService {
  private notificationSubject = new Subject<string>();
  public notifications$ = this.notificationSubject.asObservable();
  
  showNotification(message: string) {
    this.notificationSubject.next(message);
  }
}

Best Practices

  1. Prefer async pipe over manual subscriptions when possible
  2. Use takeUntil pattern for cleanup in components
  3. Avoid nested subscriptions - use operators like switchMap instead
  4. Handle errors appropriately with catchError
  5. Use debounceTime for user input to reduce API calls

Conclusion

RxJS is a powerful tool that, when used correctly, makes Angular applications more reactive, efficient, and maintainable. Master these patterns and operators to build better Angular applications.

Remember: RxJS is about thinking in streams and transformations. The more you practice, the more natural it becomes!

Previous Post

Understanding 'use client' and 'use server' in Next.js

Next Post

Exploring React 19: New Features and Improvements