HOWTO · Angular

Angular Observable VS. Promise

Compare Angular Promise and Observable behavior, including emissions, subscription, errors, cancellation, and practical use cases.

On this page

Choose a Promise for one eventual result and an Observable when a task is naturally represented as values over time. Angular does not make these abstractions interchangeable: the right choice depends on the behavior your code needs.

What Is Promise in Angular

A Promise is a JavaScript object representing one eventual outcome: fulfillment with a value or rejection with a reason. It moves from pending to fulfilled or rejected, and its then, catch, and finally methods let you compose work and handle outcomes. A Promise settles once; a fulfilled Promise does not later emit another value.

A Promise’s executor starts when the Promise is constructed. The Promise API has no built-in cancellation or retry operation, but application code can implement cancellation-aware work or retry logic around a Promise. Long chains can also become harder to follow as workflows grow.

What Is Observable in Angular

An Observable represents a stream that may emit zero or more values, then complete or error; it can also remain open indefinitely without either terminal notification. In Angular, HttpClient returns RxJS Observables. An Observable commonly begins its work when subscribed to, though this depends on how it is created. RxJS operators can transform and combine streams, and unsubscribing can stop delivery and may cancel the underlying work when that producer supports cancellation.

An Observable does not automatically provide cancellation, retries, or better performance in every case; those behaviors depend on its producer and operators. RxJS adds useful tools, with a learning cost for developers who are new to streams.

Key Differences Between Promise and Observable in Angular

Concern Promise Observable
Values One fulfillment value or rejection reason Zero or more next values; the stream may complete or error, or remain open without either notification
Start Executor runs at construction Often starts on subscription; creation determines timing
Errors Handle with catch or a rejection handler Handle in the subscription or with operators such as catchError
Cancellation No built-in cancellation method Unsubscription stops observation and can cancel supported work
Good fit One result, such as a simple computation Events, changing values, or work that benefits from RxJS composition
Retry No built-in retry; call the producing function again yourself Operators such as retry resubscribe to the source after a failure
Sharing Every handler observes the same single settled result A cold Observable runs its producer again for each subscriber; share a hot source explicitly when one run must serve many subscribers

For an Angular HTTP request, use the HttpClient Observable when you need RxJS composition or a cancellation workflow. A response can contain an array, but that does not require an Observable because it has multiple items: the request still produces one response value. A Promise can also represent that response. Prefer the abstraction that matches the task and the surrounding APIs.

Cancellation, Laziness, and Retry in Detail

Cancellation shows the sharpest practical difference. In a search-as-you-type field, each new keystroke should abandon the request for the previous term. With an HttpClient Observable, switching to a new request with switchMap unsubscribes from the old one, and HttpClient then cancels that in-flight request. A Promise offers no equivalent switch: once started, the underlying work keeps running unless the API you called provides its own cancellation handle, such as an AbortController for fetch, which is separate from the Promise itself.

Execution timing differs as well. A Promise starts its work at construction, whether or not any code ever attaches a handler, so creating the Promise is already a side effect. A cold Observable is lazy by contrast: creating it only describes the work, each subscribe call runs the producer again, and two subscribers can therefore trigger two separate executions. Code that expects one shared run must say so explicitly, for example with a sharing operator or by storing a Promise.

Retries and composition follow the same pattern. A rejected Promise is finished; recovering means calling the producing function again in your own loop. An Observable can instead resubscribe to its source with the retry operator, and timing patterns that would need hand-written timers and flags around a Promise, such as waiting for a pause in keystrokes with debounceTime or ignoring unchanged input with distinctUntilChanged, are single operators in RxJS.

Choosing in Practice

Reach for a Promise when the result is genuinely single and disposable: a one-off computation, a third-party SDK that already returns a Promise, or a short linear flow that reads best with async and await and never needs to be cancelled. Reach for an Observable when the source is a stream by nature, such as HttpClient responses you want to compose or cancel, route parameters, form value changes, DOM events, or a WebSocket, and whenever late subscribers, repeated values, or teardown matter. When in doubt, keep the type the producing API already returns instead of converting at every boundary.

Converting Between Promise and Observable

The two models interoperate, but conversion is not free. RxJS from wraps a Promise as an Observable that emits the settled value once, while firstValueFrom and lastValueFrom turn an Observable back into a Promise for async and await code. Converting an HttpClient Observable this way keeps the single response, but it gives up the cancellation that unsubscribing provided and ends further operator composition at that point. Convert at a clear boundary, such as the edge of a service, rather than inside reusable stream logic.

Common Mistakes

  • Treating an array inside one HTTP response as multiple emissions. The request still emits once; only a genuinely repeating source emits repeatedly.
  • Converting a request to a Promise and then expecting navigation away from the page to cancel it. Without a subscription to unsubscribe from, nothing signals the producer to stop.
  • Subscribing in a long-lived component without a teardown strategy, such as the async pipe or takeUntilDestroyed, so stale subscriptions keep receiving values after the view is gone.
  • Expecting a settled Promise to pick up a newer value later. A Promise memoizes its one outcome; re-reading current state requires a new call or a stream.

Example of Promise and Observable in Angular

This standalone JavaScript example uses a small teaching Observable to show subscription and multiple emissions. It is not the RxJS implementation used by Angular. The Observable sends queued and saved during subscription; the Promise callback runs afterward in a microtask.

Run this standalone example:

class Observable {
  constructor(producer) {
    this.producer = producer;
  }

  subscribe(next) {
    const teardown = this.producer(next);
    return { unsubscribe: teardown };
  }
}

const promise = Promise.resolve("saved");
const updates = new Observable((next) => {
  next("queued");
  next("saved");
  return () => {};
});

console.log("before subscription");
updates.subscribe((value) => console.log(`Observable: ${value}`));
promise.then((value) => console.log(`Promise: ${value}`));
console.log("after scheduling");
before subscription
Observable: queued
Observable: saved
after scheduling
Promise: saved

The teaching Observable calls its producer on subscription and sends two values; the Promise settles once, and its callback runs in a later microtask. In application code, use RxJS Observable and its subscription APIs. Choose a Promise for one result, and choose an Observable when stream composition, repeated emissions, or subscription management fits the task.

Example: Eager Promise, Lazy and Cancellable Observable

This second standalone example uses the same teaching Observable to show the timing and cancellation rows from the table. The Promise executor runs before anyone attaches a handler, while the Observable producer waits for subscribe and runs its teardown when the subscriber unsubscribes after the second tick. The Promise callback again runs as a microtask, before the first timer tick.

class Observable {
  constructor(producer) {
    this.producer = producer;
  }

  subscribe(next) {
    const teardown = this.producer(next);
    return { unsubscribe: teardown };
  }
}

const promise = new Promise((resolve) => {
  console.log("Promise executor: runs at construction");
  resolve("done");
});

const ticks = new Observable((next) => {
  console.log("Observable producer: runs at subscription");
  let count = 0;
  const id = setInterval(() => next(++count), 10);
  return () => {
    clearInterval(id);
    console.log("teardown: interval cleared");
  };
});

console.log("before subscription");
const sub = ticks.subscribe((value) => {
  console.log(`tick ${value}`);
  if (value === 2) sub.unsubscribe();
});
promise.then((value) => console.log(`Promise: ${value}`));
Promise executor: runs at construction
before subscription
Observable producer: runs at subscription
Promise: done
tick 1
tick 2
teardown: interval cleared

The run confirms all three behaviors in order: the executor fired at construction, the producer stayed idle until subscription, and unsubscribing ran the teardown that cleared the interval, so no further ticks arrive.