Angular RxJS Cheat Sheet
Covers core RxJS concepts, common operators, Angular HttpClient integration, the async pipe, subscription cleanup, and the different Subject types.
Core RxJS Concepts
Vocabulary underpinning every Angular data stream.
- Observable- Lazy, push-based stream of values over time; does nothing until subscribed
- Observer- Object with next/error/complete callbacks passed to subscribe()
- Subscription- Represents an active execution; call .unsubscribe() to stop and free resources
- Operator- Pure function that transforms an Observable into a new one, composed via .pipe()
- Subject- Both an Observable and an Observer; multicasts values to many subscribers
- Cold vs hot- Cold sources start producing per subscriber (e.g. HTTP); hot sources share one execution
Common Operators
A typical search-as-you-type pipeline.
import { fromEvent } from 'rxjs';import { map, filter, debounceTime, switchMap } from 'rxjs/operators';fromEvent(input, 'input').pipe( debounceTime(300), // wait for 300ms of silence map(e => (e.target as HTMLInputElement).value), filter(text => text.length > 2), // ignore short queries switchMap(query => this.searchService.search(query)) // cancel the prior request).subscribe(results => this.results = results);
Angular HttpClient with RxJS
Retry and gracefully degrade a failed request.
import { HttpClient } from '@angular/common/http';import { catchError, retry } from 'rxjs/operators';import { of } from 'rxjs';@Injectable({ providedIn: 'root' })export class UserService { constructor(private http: HttpClient) {} getUser(id: number) { return this.http.get<User>(`/api/users/${id}`).pipe( retry(2), catchError(err => { console.error('Failed to load user', err); return of(null); // fallback value instead of throwing }) ); }}
async Pipe & Subscription Cleanup
Two valid ways to avoid leaking subscriptions.
// Component: manual subscription with takeUntil cleanupexport class UserListComponent implements OnDestroy { private destroy$ = new Subject<void>(); users: User[] = []; constructor(private userService: UserService) { this.userService.getUsers().pipe( takeUntil(this.destroy$) ).subscribe(users => this.users = users); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }}// Template: async pipe subscribes AND unsubscribes automatically// <ul><li *ngFor="let user of users$ | async">{{ user.name }}</li></ul>
Subject Types
Choosing the right multicast primitive.
- Subject- No initial value, no replay; late subscribers miss past emissions
- BehaviorSubject- Requires an initial value; always replays the current/last value to new subscribers
- ReplaySubject- Replays a configurable number of past emissions (or time window) to new subscribers
- AsyncSubject- Only emits the final value, and only after the source completes
- share() operator- Converts a cold Observable into one that multicasts via an internal Subject, without creating one manually
switchMap vs mergeMap vs concatMap vs exhaustMap
Choosing the right flattening operator prevents race conditions and duplicate requests.
// switchMap: cancels the previous inner observable — ideal for search-as-you-typesearch$.pipe(switchMap(q => this.api.search(q)));// mergeMap: runs all inner observables concurrently, unordered results — parallel independent requestsuploadIds$.pipe(mergeMap(id => this.api.upload(id), 3)); // concurrency limit of 3// concatMap: queues inner observables, runs strictly in order — sequential writes that must not racesaveQueue$.pipe(concatMap(payload => this.api.save(payload)));// exhaustMap: ignores new emissions while an inner observable is active — prevents double-submitsubmitClick$.pipe(exhaustMap(() => this.api.submitForm(this.form.value)));
Combining Multiple Streams
combineLatest, withLatestFrom, and forkJoin for coordinating parallel Angular data sources.
import { combineLatest, withLatestFrom, forkJoin } from 'rxjs';// combineLatest: re-emits whenever ANY source emits, once all have emitted at least oncecombineLatest([this.filters$, this.sort$]).pipe( switchMap(([filters, sort]) => this.api.query(filters, sort))).subscribe(results => this.results = results);// withLatestFrom: sample a secondary stream's latest value only when the primary emitsthis.saveClick$.pipe( withLatestFrom(this.formValue$), map(([_, formValue]) => formValue)).subscribe(value => this.api.save(value));// forkJoin: like Promise.all — waits for all sources to COMPLETE, emits once with the last value of eachforkJoin({ user: this.api.getUser(id), posts: this.api.getPosts(id) }) .subscribe(({ user, posts }) => { this.user = user; this.posts = posts; });
Custom Operator & Exponential Backoff
Compose reusable pipelines with the pipe() free function and retryWhen-style backoff via retry({ delay }).
import { pipe, timer } from 'rxjs';import { retry, tap } from 'rxjs/operators';// Reusable operator: log + retry with capped exponential backofffunction withResilience<T>() { return pipe( tap({ error: err => console.error('stream error', err) }), retry({ count: 3, delay: (error, retryCount) => timer(Math.min(1000 * 2 ** retryCount, 8000)), }) ) as (source: Observable<T>) => Observable<T>;}this.http.get<Data>('/api/data').pipe(withResilience()).subscribe(data => this.data = data);
Angular Signals Interop (toSignal / toObservable)
Bridging RxJS streams and the newer Signals reactivity model in modern Angular.
import { toSignal, toObservable } from '@angular/core/rxjs-interop';@Component({ /* ... */ })export class UserProfileComponent { // RxJS stream -> Signal, with an initial value to avoid undefined during load user = toSignal(this.userService.getUser(this.userId), { initialValue: null }); // Signal -> Observable, e.g. to feed a signal-derived value into an existing RxJS pipeline searchTerm = signal(''); private searchTerm$ = toObservable(this.searchTerm).pipe( debounceTime(300), switchMap(term => this.api.search(term)) );}
Error-Handling Operator Reference
Operators specifically for recovering from or transforming stream errors.
- catchError- Intercepts an error and returns a replacement observable (or rethrows), stopping propagation to the subscriber's error callback
- retry(count)- Resubscribes to the source observable up to count times before letting the error through
- retry({ delay })- Configurable retry with a delay factory (timer/observable) for backoff between attempts
- finalize()- Runs a callback on completion OR error OR unsubscription, ideal for hiding a loading spinner
- timeout(ms)- Errors if the source doesn't emit within the given duration, useful for hung HTTP calls
- onErrorResumeNext- Continues to the next observable in a sequence regardless of error, ignoring the error entirely
Prefer the async pipe over manual .subscribe() in components whenever the value just needs rendering in the template — it unsubscribes automatically on component destroy, eliminating a whole class of memory leaks that takeUntil-based cleanup is easy to forget.