Promises and Observables in Angular: Key Differences and When to Use Each
Promises are eager, single-value containers without built-in cancellation, while Observables are lazy, multi-value streams with powerful composition operators and automatic cancellation support.
Angular's HttpClient and core asynchronous services are built on RxJS Observables rather than native JavaScript Promises. While both abstractions handle asynchronous operations, they differ fundamentally in execution timing, cancellation capabilities, and value emission patterns. Understanding when to use promises and observables in Angular ensures you leverage the framework's reactive architecture effectively while avoiding subtle bugs like memory leaks or redundant HTTP requests.
Execution Model: Eager vs. Lazy Evaluation
Promise Eager Execution
Promises execute immediately upon creation. When you call new Promise(...), the underlying asynchronous operation starts right away, regardless of whether any code consumes the result. This eager nature makes Promises simple but inflexible for scenarios requiring conditional execution or cancellation.
Observable Lazy Execution
Observables are lazy – the producer code runs only when a consumer subscribes via observable.subscribe(...). In packages/common/http/src/client.ts (lines 33-41), Angular's HttpClient returns cold Observables that trigger the actual HTTP request only upon subscription. This lazy evaluation prevents unnecessary network traffic when components destroy before subscribing or when using the async pipe in templates.
Value Emission and Cancellation
Single vs. Multiple Values
Promises resolve exactly once (or reject once) with a single value. Observables can emit zero, one, or many values over time through the next, error, and complete callbacks. This capability is essential for Angular features like reactive forms (valueChanges), WebSocket connections, and router events that emit multiple values throughout the application lifecycle.
Cancellation Support
Promises lack built-in cancellation; you must ignore the result or use AbortController (not native to Promise itself). Observables provide automatic cancellation through unsubscription. In packages/common/http/src/xhr.ts (lines 154-165), Angular's XHR handler aborts the underlying HTTP request when the subscriber unsubscribes. This prevents memory leaks and unnecessary processing when users navigate away from components before requests complete.
Composition and Operators
Promise Chaining Limitations
Promises offer basic composition through .then(), .catch(), and .finally(). While adequate for simple sequential operations, these methods lack built-in support for complex transformations, error retry logic, or stream debouncing without external libraries.
RxJS Operator Pipelines
Observables integrate with RxJS operators through the .pipe() method, enabling sophisticated composition patterns:
map– Transform emitted valuesswitchMap– Cancel previous inner observables when new values emit (perfect for search type-ahead)retry– Automatically retry failed requeststakeUntil– Cancel streams based on lifecycle eventsdebounceTime– Control emission frequency
These operators allow declarative expression of complex asynchronous workflows that would require verbose imperative code with Promises.
Angular's Internal Implementation
HttpClient Observable Architecture
The HttpClient service in packages/common/http/src/client.ts (lines 33-41) returns Observable<HttpEvent<R>> rather than Promises. This design ensures that HTTP requests remain cold observables, executing only when subscribed to and supporting Angular's automatic cancellation mechanisms.
XHR Abort on Unsubscribe
The XHR backend implementation in packages/common/http/src/xhr.ts (lines 154-165) demonstrates how Angular handles cancellation. When an Observable subscription ends (either through unsubscription or component destruction), the underlying XMLHttpRequest aborts immediately, freeing browser resources and network bandwidth.
Router Promise Utilities
For interoperability, Angular provides conversion utilities in packages/router/src/utils/collection.ts (lines 88-92). The wrapIntoPromise function detects Observables and converts them using firstValueFrom, allowing router guards and resolvers to work with both Promise and Observable-based APIs while maintaining internal consistency.
Practical Usage Patterns
HTTP Requests with Observables
For standard HTTP operations, use Observables with the async pipe or explicit subscription management:
import { HttpClient } from '@angular/common/http';
import { catchError, map } from 'rxjs/operators';
import { of } from 'rxjs';
@Injectable({providedIn: 'root'})
export class HeroService {
constructor(private http: HttpClient) {}
getHeroes() {
return this.http.get<Hero[]>('/api/heroes')
.pipe(
map(heroes => heroes.filter(h => h.active)),
catchError(err => {
console.error('Failed to load heroes', err);
return of([]); // fallback observable
})
);
}
}
The request executes only when a component subscribes, and unsubscribing automatically aborts the underlying XHR.
Converting to Promises
When integrating with async/await syntax or third-party libraries expecting Promises, convert Observables using firstValueFrom:
import { firstValueFrom } from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Injectable({providedIn: 'root'})
export class ConfigService {
constructor(private http: HttpClient) {}
async loadConfig(): Promise<AppConfig> {
// Converts Observable to Promise, taking only the first emitted value
return await firstValueFrom(this.http.get<AppConfig>('/api/config'));
}
}
Note that toPromise() is deprecated; firstValueFrom and lastValueFrom are the modern equivalents.
Cancellation Patterns with takeUntil
For components with finite lifecycles, use the takeUntil pattern to prevent memory leaks:
import { Component, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-search',
template: `<div *ngFor="let result of results">{{result.name}}</div>`
})
export class SearchComponent implements OnDestroy {
private destroy$ = new Subject<void>();
results: SearchResult[] = [];
constructor(private http: HttpClient) {
this.http.get<SearchResult[]>('/api/search')
.pipe(takeUntil(this.destroy$))
.subscribe(result => {
this.results = result;
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
When the component destroys, the subscription terminates and the underlying HTTP request aborts automatically.
Summary
- Promises execute eagerly, resolve single values, and lack native cancellation mechanisms.
- Observables execute lazily upon subscription, support multiple value emissions, and provide automatic cancellation through unsubscription.
- Angular's
HttpClientinpackages/common/http/src/client.tsreturns Observables to enable lazy execution and cancellation as implemented inpackages/common/http/src/xhr.ts. - Use Observables for HTTP requests, event streams, and complex async coordination requiring operators like
switchMaporretry. - Use Promises only when integrating with
async/awaitsyntax or third-party libraries expecting Promise-based APIs, converting viafirstValueFrom.
Frequently Asked Questions
Should I use Promises or Observables for HTTP calls in Angular?
You should use Observables for HTTP calls in Angular. The HttpClient service returns Observables by default because they support lazy execution (requests only fire on subscription), automatic cancellation when components destroy, and powerful RxJS operators for error handling and retry logic. While you can convert to Promises using firstValueFrom, you lose the ability to cancel in-flight requests and chain reactive operators.
How do I cancel an HTTP request in Angular?
Cancel HTTP requests by unsubscribing from the Observable. When you call subscription.unsubscribe() or use the takeUntil operator with a destruction subject, Angular's XHR backend in packages/common/http/src/xhr.ts automatically aborts the underlying XMLHttpRequest. This prevents memory leaks and unnecessary network traffic when users navigate away from components before requests complete.
Can I convert an Observable to a Promise in Angular?
Yes, convert Observables to Promises using firstValueFrom or lastValueFrom from the rxjs package. The deprecated toPromise() method should be avoided in modern Angular applications. For example, await firstValueFrom(http.get('/api/data')) returns the first emitted value as a Promise. Angular's router uses similar conversion logic in packages/router/src/utils/collection.ts via the wrapIntoPromise utility to handle both Observable and Promise-based route guards uniformly.
Why does Angular use RxJS Observables instead of native Promises?
Angular uses RxJS Observables because they provide lazy execution, cancellation, and composition capabilities that Promises lack. The framework needs to support complex scenarios like retrying failed requests, debouncing user input, and automatically canceling HTTP requests when components destroy. Observables align with Angular's change detection system through Zone.js integration and enable the powerful async pipe for automatic subscription management in templates. These features would be impossible or cumbersome to implement with native Promises alone.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →