Skip to content

Angular Observables

Observables are the core primitive behind RxJS and much of Angular's asynchronous plumbing. This lesson covers creating, subscribing to, and properly cleaning up Observables.

What Is an Observable?

An Observable represents a stream of values that arrive over time. It can emit any number of values (including zero), and can either complete successfully, error out, or continue indefinitely, unlike a Promise, which always resolves to exactly one value (or rejects) and then is done.

Angular's HttpClient methods, router events, and reactive form value changes are all Observables, understanding how to create and consume them is foundational to working effectively with Angular.

import { Observable } from 'rxjs';

const greeting$ = new Observable<string>(subscriber => {
  subscriber.next('Hello');
  subscriber.next('World');
  subscriber.complete();
});

greeting$.subscribe(value => console.log(value));
// logs: Hello, World

The $ suffix on greeting$ is a common naming convention signaling "this variable is an Observable".

Common Observable Creation Functions

of(1, 2, 3)                 // emits each argument, then completes
from([1, 2, 3])              // emits each array item, then completes
from(promise)                 // wraps a Promise as an Observable
interval(1000)                // emits an incrementing number every second
fromEvent(el, 'click')        // wraps a DOM event as an Observable
  • of() creates an Observable that synchronously emits a fixed set of values then completes.
  • from() converts an array, iterable, or Promise into an Observable.
  • interval() and timer() create Observables that emit on a schedule.
  • fromEvent() wraps DOM (or Node) event emitters as Observables.

Observables Cheatsheet

Creation functions and subscription patterns.

Function/Method Example Purpose
of(...) of(1, 2, 3) Emit fixed values synchronously
from(...) from(somePromise) Convert array/promise/iterable to Observable
interval(ms) interval(1000) Emit incrementing numbers on an interval
fromEvent(el, type) fromEvent(button, 'click') Wrap a DOM event stream
.subscribe(fn) obs$.subscribe(v => ...) Start listening for emitted values
.subscribe({...}) { next, error, complete } Handle all three notification types
.unsubscribe() sub.unsubscribe() Stop listening and release resources

Observables vs Promises

Promises and Observables both represent future values, but differ in important ways that matter for real applications.

Promise Observable
Values emitted Exactly one (or rejects) Zero, one, or many, over time
Execution start Immediately, on creation Only when subscribed to
Cancellable No Yes, via unsubscribe
Operators None built-in (.then/.catch only) Rich set (map, filter, debounce, retry, ...)

Subscribing and Unsubscribing Correctly

Every subscription should eventually be cleaned up if the Observable doesn't complete on its own, otherwise, you risk memory leaks or logic running against a destroyed component.

export class LiveClockComponent implements OnDestroy {
  time = '';
  private sub = interval(1000).subscribe(() => {
    this.time = new Date().toLocaleTimeString();
  });

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

Observables that complete on their own (like a typical HTTP request) don't strictly need manual unsubscription, but it's still a safe default habit for long-lived streams.

Simplifying Cleanup With takeUntilDestroyed()

Rather than manually tracking a Subscription object and calling .unsubscribe() in ngOnDestroy, the takeUntilDestroyed() RxJS interop operator automatically completes the source Observable when the component (or any injection context) is destroyed.

export class LiveClockComponent {
  private destroyRef = inject(DestroyRef);
  time = '';

  constructor() {
    interval(1000)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => (this.time = new Date().toLocaleTimeString()));
  }
}

Common Mistakes

  • Treating an Observable as if it only ever emits once, like a Promise, missing later emissions.
  • Forgetting to unsubscribe from long-lived or infinite Observables, leaking memory and running stale logic.
  • Creating a new Observable manually with the Observable constructor when a simpler creation function (of, from, fromEvent) already covers the case.
  • Subscribing multiple times to the same "cold" Observable when a single shared subscription (with shareReplay) was intended.

Key Takeaways

  • Observables represent a stream of values over time and can emit zero, one, or many values.
  • Creation functions like of, from, interval, and fromEvent cover most common Observable sources.
  • Unlike Promises, Observables are cancellable via .unsubscribe() and support a large library of operators.
  • takeUntilDestroyed() is the modern, low-boilerplate way to clean up subscriptions tied to a component's lifetime.

Pro Tip

Reach for takeUntilDestroyed() by default for subscriptions created inside components, it removes an entire category of memory leaks without the boilerplate of manually storing and unsubscribing a Subscription in ngOnDestroy.