Skip to main content
An Observable is the core building block of RxJS. It represents a lazy Push collection of multiple values that can be delivered synchronously or asynchronously over time.

What is an Observable?

Observables fill a unique role in JavaScript’s data production paradigm:
SingleMultiple
PullFunctionIterator
PushPromiseObservable
Key Insight: An Observable is like a function that can return multiple values over time, both synchronously and asynchronously.

Pull vs Push Systems

Pull Systems: The consumer decides when to receive data from the producer.
  • Functions: You call them to get a single value
  • Iterators: You call next() to get multiple values
Push Systems: The producer decides when to send data to the consumer.
  • Promises: Deliver one value when ready
  • Observables: Deliver zero to infinite values when ready

Type Signature

class Observable<T> {
  constructor(subscribe?: (subscriber: Subscriber<T>) => TeardownLogic)
  
  subscribe(observer: Partial<Observer<T>>): Subscription
  subscribe(next: (value: T) => void): Subscription
  
  pipe<A>(op1: OperatorFunction<T, A>): Observable<A>
  pipe<A, B>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>): Observable<B>
  // ... additional pipe overloads
}

type TeardownLogic = Subscription | Unsubscribable | (() => void) | void

Creating Observables

Using the Constructor

The Observable constructor takes a subscribe function that defines how values are produced:
import { Observable } from 'rxjs';

const observable = new Observable((subscriber) => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() => {
    subscriber.next(4);
    subscriber.complete();
  }, 1000);
});

Using Creation Operators

Most commonly, you’ll create Observables using creation operators like of, from, interval, etc.
import { of } from 'rxjs';

const numbers$ = of(1, 2, 3, 4, 5);

Subscribing to Observables

observable.subscribe({
  next(x) {
    console.log('got value ' + x);
  },
  error(err) {
    console.error('something wrong occurred: ' + err);
  },
  complete() {
    console.log('done');
  },
});
Subscribing to an Observable starts its execution. Each subscription creates an independent execution.

Observable Execution

Notification Types

An Observable execution can deliver three types of notifications:
  1. Next: Sends a value (Number, String, Object, etc.)
  2. Error: Sends a JavaScript Error (terminates the execution)
  3. Complete: Sends no value (terminates the execution)

Observable Contract

All Observable executions follow this pattern (expressed as a regular expression):
next*(error|complete)?
This means:
  • Zero to infinite Next notifications
  • Optionally followed by either one Error OR one Complete
  • Nothing can be delivered after Error or Complete
import { Observable } from 'rxjs';

const observable = new Observable((subscriber) => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
  subscriber.next(4); // Never delivered - violates the contract
});

Synchronous vs Asynchronous

Observables can deliver values either synchronously or asynchronously - they are not inherently async.
import { Observable } from 'rxjs';

const sync$ = new Observable((subscriber) => {
  subscriber.next(42);
});

console.log('before');
sync$.subscribe(x => console.log(x));
console.log('after');

// Output:
// before
// 42
// after

Disposing Observable Executions

Subscribing returns a Subscription object that can be used to cancel the execution:
import { interval } from 'rxjs';

const subscription = interval(1000).subscribe(x => console.log(x));

// Later: stop the execution
subscription.unsubscribe();

Cleanup Function

When creating an Observable, return a cleanup function to release resources:
import { Observable } from 'rxjs';

const observable = new Observable((subscriber) => {
  const intervalId = setInterval(() => {
    subscriber.next('hi');
  }, 1000);

  // Cleanup function
  return () => {
    clearInterval(intervalId);
  };
});

const subscription = observable.subscribe(x => console.log(x));

// Calling unsubscribe() will execute the cleanup function
subscription.unsubscribe();

Observables vs Functions

Observables are like functions with superpowers:
function foo() {
  console.log('Hello');
  return 42;
  // Cannot return another value
}

const x = foo(); // 42
const y = foo(); // 42
Both functions and Observables are lazy - they don’t execute until called/subscribed.

Observables vs Promises

FeaturePromiseObservable
ValuesSingle valueMultiple values
LazyNo (eager)Yes (lazy)
CancellableNoYes (via unsubscribe)
OperatorsLimited (then, catch)Extensive library
MulticastYesNo (unless using Subject)

Common Patterns

Error Handling

import { Observable } from 'rxjs';

const observable = new Observable((subscriber) => {
  try {
    subscriber.next(1);
    subscriber.next(2);
    // Risky operation
    throw new Error('Something went wrong');
  } catch (err) {
    subscriber.error(err);
  }
});

observable.subscribe({
  next: x => console.log(x),
  error: err => console.error('Error:', err.message)
});

Finite Streams

import { Observable } from 'rxjs';

const finite$ = new Observable<number>((subscriber) => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete(); // Signal completion
});

Infinite Streams

import { interval } from 'rxjs';

const infinite$ = interval(1000); // Never completes
// Must unsubscribe to stop

Best Practices

Naming Convention: Suffix Observable variable names with $ (e.g., data$, clicks$) to distinguish them from regular values.
  1. Always handle errors - Unhandled errors can crash your application
  2. Unsubscribe when done - Prevent memory leaks in long-lived components
  3. Use creation operators - Prefer of, from, etc. over new Observable
  4. Keep subscribe logic minimal - Use operators for transformations
  5. Return cleanup functions - Always clean up resources like timers and event listeners

When to Use Observables

Use Observables when you need:
  • Multiple values over time
  • Cancellation support
  • Lazy execution
  • Powerful composition via operators
  • Complex async coordination
Examples:
  • User input events
  • WebSocket messages
  • HTTP polling
  • Real-time data streams
  • Animation sequences
  • Observer - Consumes values from Observables
  • Subscription - Manages Observable execution lifecycle
  • Operators - Transform and compose Observables
  • Subject - Special Observable that multicasts to multiple Observers