Essential RxJS Operators
12 min read
The Operators You Will Use Most
RxJS ships with over 100 operators but a handful cover 90% of real-world Angular needs. Understanding when to use which flattening operator is the single biggest RxJS skill to develop.
Transformation: map and filter
ts
import { of } from 'rxjs';
import { map, filter } from 'rxjs/operators';
const prices$ = of(10, 25, 5, 40, 3);
prices$
.pipe(
filter(price => price >= 10), // keep only prices >= 10
map(price => price * 1.1) // apply 10% markup
)
.subscribe(console.log);
// 11, 27.5, 44Flattening: switchMap, mergeMap, concatMap, exhaustMap
- `switchMap` — cancels the previous inner Observable on each new emission. Use for live search, route-driven data fetching.
- `mergeMap` — runs all inner Observables concurrently. Use for parallel fire-and-forget operations.
- `concatMap` — queues inner Observables one after another. Use when order matters (e.g. sequential API calls).
- `exhaustMap` — ignores new emissions while an inner Observable is active. Use for login button clicks to prevent double-submit.
ts
import { Component, OnInit, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { AsyncPipe } from '@angular/common';
import {
Observable, of,
debounceTime, distinctUntilChanged, switchMap, catchError
} from 'rxjs';
interface Repo { id: number; full_name: string; stargazers_count: number; }
@Component({
selector: 'app-repo-search',
standalone: true,
imports: [ReactiveFormsModule, AsyncPipe],
template: `
<input [formControl]="query" placeholder="Search GitHub repos" />
<ul>
@for (repo of repos$ | async; track repo.id) {
<li>{{ repo.full_name }} ({{ repo.stargazers_count }} stars)</li>
}
</ul>
`
})
export class RepoSearchComponent implements OnInit {
private http = inject(HttpClient);
query = new FormControl('');
repos$!: Observable<Repo[]>;
ngOnInit() {
this.repos$ = this.query.valueChanges.pipe(
debounceTime(350),
distinctUntilChanged(),
switchMap(term =>
term
? this.http
.get<{ items: Repo[] }>(`https://api.github.com/search/repositories?q=${term}&per_page=5`)
.pipe(map(r => r.items), catchError(() => of([])))
: of([])
)
);
}
}debounceTime and distinctUntilChanged
debounceTime(ms) waits until no new value arrives for ms milliseconds before emitting — preventing a network call on every keystroke. distinctUntilChanged() skips emission when the new value equals the previous one — avoiding duplicate requests when the user types then backtracks.
Always pair debounceTime + distinctUntilChanged + switchMap for typeahead/live-search patterns. This trio is so common it has become an Angular idiom.
Ready to test yourself?
Practice Angular— quiz & coding exercises