Essential RxJS Operators: Complete Guide to Creation, Filtering, and Utility Operators
🛠️ Essential RxJS Operators
Beyond the higher-order mapping operators, RxJS provides a rich set of operators for creating, filtering, transforming, and managing Observable streams. This guide covers the most important operators you'll use in real-world applications.
🔄 Creation Operators
of() - Create Observable from Values
import { of } from 'rxjs';
of(1, 2, 3, 4, 5).subscribe(console.log);
// Output: 1, 2, 3, 4, 5
of({ name: 'John' }, { name: 'Jane' }).subscribe(console.log);
// Output: { name: 'John' }, { name: 'Jane' }
from() - Convert Array/Promise to Observable
import { from } from 'rxjs';
// From array
from([1, 2, 3]).subscribe(console.log);
// Output: 1, 2, 3
// From Promise
from(fetch('/api/users')).subscribe(response => console.log(response));
interval() - Emit Numbers at Regular Intervals
import { interval } from 'rxjs';
import { take } from 'rxjs/operators';
interval(1000).pipe(
take(5) // Only take first 5 emissions
).subscribe(n => console.log(`Timer: ${n}`));
// Output: Timer: 0, Timer: 1, Timer: 2, Timer: 3, Timer: 4
timer() - Emit After Delay
import { timer } from 'rxjs';
// Emit after 3 seconds, then every 1 second
timer(3000, 1000).pipe(
take(3)
).subscribe(n => console.log(`Delayed timer: ${n}`));
🎯 Filtering Operators
filter() - Filter Values Based on Condition
import { of } from 'rxjs';
import { filter } from 'rxjs/operators';
of(1, 2, 3, 4, 5, 6).pipe(
filter(n => n % 2 === 0) // Only even numbers
).subscribe(console.log);
// Output: 2, 4, 6
// Real example: Filter valid form inputs
formInput$.pipe(
filter(value => value.length >= 3)
).subscribe(validInput => console.log(validInput));
take() - Take Only First N Values
import { interval } from 'rxjs';
import { take } from 'rxjs/operators';
interval(1000).pipe(
take(3) // Only first 3 values
).subscribe(console.log);
// Output: 0, 1, 2 (then completes)
takeUntil() - Take Until Another Observable Emits
import { interval, fromEvent } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
const stop$ = fromEvent(stopButton, 'click');
interval(1000).pipe(
takeUntil(stop$) // Stop when button is clicked
).subscribe(console.log);
// Common pattern for component cleanup
private destroy$ = new Subject();
ngOnInit() {
interval(1000).pipe(
takeUntil(this.destroy$)
).subscribe(console.log);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
distinctUntilChanged() - Skip Duplicate Consecutive Values
import { of } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
of(1, 1, 2, 2, 2, 3, 3, 1).pipe(
distinctUntilChanged()
).subscribe(console.log);
// Output: 1, 2, 3, 1
// Real example: Search input
searchInput$.pipe(
distinctUntilChanged(), // Don't search for same term
debounceTime(300)
).subscribe(query => search(query));
skip() - Skip First N Values
import { of } from 'rxjs';
import { skip } from 'rxjs/operators';
of(1, 2, 3, 4, 5).pipe(
skip(2) // Skip first 2 values
).subscribe(console.log);
// Output: 3, 4, 5
⏰ Time-Based Operators
debounceTime() - Emit Only After Silence Period
import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';
// Search autocomplete
fromEvent(searchInput, 'input').pipe(
map(event => event.target.value),
debounceTime(300) // Wait 300ms after user stops typing
).subscribe(query => performSearch(query));
// Timeline visualization:
// Input: a-ab-abc----abcd----|
// Output: --------abc----abcd-|
throttleTime() - Emit at Most Once Per Time Period
import { fromEvent } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
// Button click protection
fromEvent(button, 'click').pipe(
throttleTime(1000) // At most one click per second
).subscribe(() => handleClick());
// Timeline visualization:
// Input: a-b-c-d-e-f-g-h----|
// Output: a-------e----------|
delay() - Delay Emissions
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
of('Hello', 'World').pipe(
delay(2000) // Delay all emissions by 2 seconds
).subscribe(console.log);
timeout() - Error if No Emission Within Time
import { timer } from 'rxjs';
import { timeout, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
timer(5000).pipe(
timeout(3000), // Timeout after 3 seconds
catchError(error => of('Request timed out'))
).subscribe(console.log);
// Output: 'Request timed out'
🔄 Transformation Operators
scan() - Accumulate Values Over Time
import { of } from 'rxjs';
import { scan } from 'rxjs/operators';
// Running total
of(1, 2, 3, 4, 5).pipe(
scan((acc, value) => acc + value, 0)
).subscribe(console.log);
// Output: 1, 3, 6, 10, 15
// Real example: Shopping cart total
cartItems$.pipe(
scan((total, item) => total + item.price, 0)
).subscribe(total => updateCartTotal(total));
reduce() - Accumulate and Emit Final Result
import { of } from 'rxjs';
import { reduce } from 'rxjs/operators';
of(1, 2, 3, 4, 5).pipe(
reduce((acc, value) => acc + value, 0)
).subscribe(console.log);
// Output: 15 (only final sum)
pluck() - Extract Property from Objects
import { of } from 'rxjs';
import { pluck } from 'rxjs/operators';
of(
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 }
).pipe(
pluck('name')
).subscribe(console.log);
// Output: 'John', 'Jane'
🔀 Combination Operators
startWith() - Start with Initial Value
import { of } from 'rxjs';
import { startWith } from 'rxjs/operators';
of(2, 3, 4).pipe(
startWith(1)
).subscribe(console.log);
// Output: 1, 2, 3, 4
// Real example: Loading state
dataStream$.pipe(
startWith({ loading: true })
).subscribe(state => updateUI(state));
zip() - Combine Observables by Index
import { of, zip } from 'rxjs';
const first$ = of(1, 2, 3);
const second$ = of('a', 'b', 'c');
zip(first$, second$).subscribe(console.log);
// Output: [1, 'a'], [2, 'b'], [3, 'c']
// Real example: Combine user data
zip(
http.get('/api/user'),
http.get('/api/user/preferences')
).subscribe(([user, preferences]) => {
console.log({ user, preferences });
});
withLatestFrom() - Combine with Latest from Another Stream
import { fromEvent, interval } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';
const clicks$ = fromEvent(button, 'click');
const timer$ = interval(1000);
clicks$.pipe(
withLatestFrom(timer$)
).subscribe(([click, timerValue]) => {
console.log(`Clicked at timer: ${timerValue}`);
});