ANGULAR: Angular Observables and RxJS

Observables are Angular's primary mechanism for handling asynchronous data. Built on the RxJS library, they allow components to react to data streams from HTTP requests, user events, and other asynchronous sources without blocking the application.

Observables are a way of dealing with asynchronous data — data that does not arrive instantly but takes time to come back from a server, a user event, or another source. The key principle is to not pause your application while waiting for the work to complete.

JavaScript has evolved several approaches to handle asynchronous operations:

ApproachDescriptionIssue
CallbacksFunctions passed as arguments, called when data arrivesLeads to "callback hell" — deeply nested, hard-to-read code
PromisesA cleaner abstraction with built-in error handling (.catch) and composabilityCan only resolve once; not well-suited for streams of events
ObservablesA larger version of Promises — handles streams, multiple values, and complex async pipelinesRequires the RxJS library

The Problem with Synchronous Data Fetching

Without observables, fetching data from a server can block the entire application:

typescript
// Blocking - application waits until data arrives
let userData = getUserDataFromServer(); // can take 1-2 seconds
console.log(userData); // nothing else runs until this line finishes

This freezes the UI and creates a poor user experience.


subscribe

The solution is to make the call observable and subscribe to the result. The application continues running, and the callback fires when the data is ready.

typescript
let userDataObs = getUserDataObservable();

userDataObs.subscribe(userData => {
  // This runs when the data arrives — not immediately
  console.log(userData);
});

// Application continues here without waiting

Full Subscribe Syntax

The subscribe method accepts an observer object with three optional callbacks:

typescript
let userDataObs = getUserDataObservable();

userDataObs.subscribe({
  next: userData => {
    // Called when new data arrives
    console.log(userData);
  },
  error: error => {
    // Called if an error occurs
    console.error('Error fetching user data:', error);
  },
  complete: () => {
    // Called when the observable completes (not always used)
    console.log('Data stream complete');
  }
});

CallbackWhen It Fires
nextEach time a new value is emitted
errorIf an error occurs in the stream
completeWhen the observable finishes emitting values

pipe

The pipe method transforms the data before it reaches the subscribe callback. It is the correct place to process, filter, or transform data from an observable — especially useful when multiple components subscribe to the same stream.

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

let numberObs = from([1, 2, 3, 4]);

numberObs
  .pipe(
    map(num => num + 1)  // adds 1 to every value
  )
  .subscribe(data => console.log(data)); // outputs: 2, 3, 4, 5

Common RxJS Operators

OperatorDescription
mapTransform each emitted value
filterOnly pass values that match a condition
tapSide-effect without transforming (useful for logging)
catchErrorHandle errors in the stream
switchMapMap to a new observable, cancelling the previous
debounceTimeWait before emitting (useful for search input)

Creating Observables with BehaviorSubject

BehaviorSubject is a special type of observable that holds a current value and emits it to any new subscriber immediately. It is commonly used for shared state in services.

typescript
import { BehaviorSubject } from 'rxjs';

// Create a BehaviorSubject with an initial value
let cartCount = new BehaviorSubject<number>(0);

// Subscribe to changes
cartCount.subscribe(count => {
  console.log('Cart count:', count);
});

// Emit a new value
cartCount.next(1);  // logs: Cart count: 1
cartCount.next(2);  // logs: Cart count: 2

BehaviorSubject is ideal for state like:

  • Shopping cart item count
  • Current logged-in user
  • App-wide loading state

Observables in an Angular Service

A typical Angular service using BehaviorSubject for shared state:

typescript
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { IProduct } from './product.model';

@Injectable({
  providedIn: 'root'
})
export class CartService {
  private cartItems$ = new BehaviorSubject<IProduct[]>([]);

  getCartItems(): Observable<IProduct[]> {
    return this.cartItems$.asObservable();
  }

  addItem(product: IProduct): void {
    const current = this.cartItems$.getValue();
    this.cartItems$.next([...current, product]);
  }
}

Consuming the observable in a component:

typescript
export class CartComponent implements OnInit, OnDestroy {
  cartItems: IProduct[] = [];
  private subscription: Subscription;

  constructor(private cartService: CartService) {}

  ngOnInit(): void {
    this.subscription = this.cartService.getCartItems()
      .subscribe(items => {
        this.cartItems = items;
      });
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe(); // prevent memory leaks
  }
}


Observable Data Flow

md
Data Source (API/Event)
          |
          v
    Observable created
          |
          | pipe()
          v
    Transform / Filter
    (map, filter, etc.)
          |
          | subscribe()
          v
    Component receives data
    and updates the UI


Summary

Observables are the foundation of asynchronous programming in Angular. They replace blocking code with reactive data streams that let the application remain responsive at all times.

Key concepts to remember:

  • subscribe — attach a callback that fires when data arrives
  • pipe — transform data before it reaches the subscriber
  • BehaviorSubject — create your own observable for shared state
  • Always unsubscribe in ngOnDestroy to prevent memory leaks

Observables and RxJS are what make Angular applications reactive — master them early.