Skip to main content

Quick Start Guide

This guide will get you up and running with RxJS in minutes. You’ll learn how to create Observables, transform data with operators, and handle real-world scenarios.
Make sure you’ve installed RxJS before following this guide.

Your First Observable

Let’s start with the simplest possible Observable that emits a sequence of values.
1

Create a new file

Create a file called app.ts (or app.js if not using TypeScript):
app.ts
import { of } from 'rxjs';

// Create an Observable that emits three values
const numbers$ = of(10, 20, 30);

// Subscribe to receive the values
numbers$.subscribe({
  next: value => console.log('Received:', value),
  error: err => console.error('Error:', err),
  complete: () => console.log('Complete!')
});
The $ suffix is a common naming convention for Observables, making them easy to identify in your code.
2

Run your code

Execute your file:
node app.js
3

See the output

You should see:
Received: 10
Received: 20
Received: 30
Complete!
Congratulations! You’ve created your first Observable.

Understanding the Observable

An Observable is a lazy collection of values over time. Let’s break down the anatomy:
import { Observable } from 'rxjs';

// Creating an Observable from scratch
const custom$ = new Observable(subscriber => {
  // Emit values
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  
  // Signal completion
  subscriber.complete();
  
  // Cleanup function (optional)
  return () => {
    console.log('Cleanup!');
  };
});

custom$.subscribe({
  next: value => console.log('Value:', value),
  error: err => console.error('Error:', err),
  complete: () => console.log('Done!')
});

Practical Example: Timer

Let’s create a timer that emits values every second:
timer.ts
import { interval, take } from 'rxjs';

// Emit a value every 1000ms (1 second)
const timer$ = interval(1000);

// Take only the first 5 values
const limitedTimer$ = timer$.pipe(take(5));

limitedTimer$.subscribe({
  next: value => console.log(`Tick ${value}`),
  complete: () => console.log('Timer finished!')
});

// Output:
// Tick 0
// Tick 1
// Tick 2
// Tick 3
// Tick 4
// Timer finished!
The interval function creates an Observable that emits sequential numbers starting from 0. The take operator limits the number of emissions.

Transforming Data with Operators

Operators are the real power of RxJS. They let you transform, filter, and combine Observables.
1

Map - Transform values

import { of, map } from 'rxjs';

of(1, 2, 3, 4, 5)
  .pipe(
    map(x => x * 10)
  )
  .subscribe(value => console.log(value));

// Output: 10, 20, 30, 40, 50
2

Filter - Remove values

import { of, filter } from 'rxjs';

of(1, 2, 3, 4, 5)
  .pipe(
    filter(x => x % 2 === 0)
  )
  .subscribe(value => console.log(value));

// Output: 2, 4
3

Chain multiple operators

import { of, map, filter } from 'rxjs';

of(1, 2, 3, 4, 5)
  .pipe(
    map(x => x * 10),        // Multiply by 10
    filter(x => x > 20),     // Keep values > 20
    map(x => `Value: ${x}`)  // Format as string
  )
  .subscribe(value => console.log(value));

// Output:
// Value: 30
// Value: 40
// Value: 50

Real-World Example: Search Input

Here’s a complete example showing how to handle a search input with debouncing and API calls:
search.ts
import { fromEvent, map, debounceTime, distinctUntilChanged, switchMap, catchError } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { of } from 'rxjs';

// Get reference to input element
const searchInput = document.getElementById('search') as HTMLInputElement;

// Create observable from input events
const search$ = fromEvent(searchInput, 'input').pipe(
  // Extract the input value
  map(event => (event.target as HTMLInputElement).value),
  
  // Wait 300ms after user stops typing
  debounceTime(300),
  
  // Only emit if value changed
  distinctUntilChanged(),
  
  // Switch to new search, canceling previous
  switchMap(searchTerm => {
    if (searchTerm.length === 0) {
      return of([]);
    }
    
    return ajax(`https://api.example.com/search?q=${searchTerm}`).pipe(
      map(response => response.response),
      catchError(error => {
        console.error('Search failed:', error);
        return of([]);
      })
    );
  })
);

// Subscribe to search results
search$.subscribe(results => {
  console.log('Search results:', results);
  // Update UI with results
});
What’s happening here?
  1. fromEvent: Converts DOM events into an Observable
  2. debounceTime: Waits for user to stop typing (300ms)
  3. distinctUntilChanged: Ignores duplicate search terms
  4. switchMap: Cancels previous API call if user types again
  5. catchError: Handles API errors gracefully

Complete Practical Example: Counter

Let’s build a simple counter application that demonstrates multiple RxJS concepts:
counter.ts
import { fromEvent, merge, scan, map, startWith } from 'rxjs';

// HTML Setup
// <button id="increment">+1</button>
// <button id="decrement">-1</button>
// <button id="reset">Reset</button>
// <div id="count">0</div>

// Get button references
const incrementBtn = document.getElementById('increment')!;
const decrementBtn = document.getElementById('decrement')!;
const resetBtn = document.getElementById('reset')!;
const countDisplay = document.getElementById('count')!;

// Create observables from button clicks
const increment$ = fromEvent(incrementBtn, 'click').pipe(map(() => 1));
const decrement$ = fromEvent(decrementBtn, 'click').pipe(map(() => -1));
const reset$ = fromEvent(resetBtn, 'click').pipe(map(() => 0));

// Merge all button clicks into a single stream
const counter$ = merge(
  increment$,
  decrement$,
  reset$.pipe(map(() => null)) // null signals reset
).pipe(
  // Accumulate the count
  scan((acc, value) => {
    return value === null ? 0 : acc + value;
  }, 0),
  // Start with initial value
  startWith(0)
);

// Subscribe and update the display
counter$.subscribe(count => {
  countDisplay.textContent = String(count);
});
Key patterns used:
  • merge: Combines multiple Observables into one
  • scan: Accumulates values over time (like Array.reduce)
  • startWith: Emits an initial value immediately

Error Handling

Always handle errors in your Observables:
import { throwError, of, catchError, retry } from 'rxjs';
import { ajax } from 'rxjs/ajax';

ajax('https://api.example.com/data').pipe(
  // Retry failed requests up to 3 times
  retry(3),
  
  // Catch any remaining errors
  catchError(error => {
    console.error('Request failed:', error);
    // Return a fallback value
    return of({ data: [], error: true });
  })
).subscribe(result => {
  console.log('Result:', result);
});

Unsubscribing

Always clean up subscriptions to prevent memory leaks:
import { interval } from 'rxjs';

const subscription = interval(1000).subscribe(value => {
  console.log(value);
});

// Later, clean up
setTimeout(() => {
  subscription.unsubscribe();
  console.log('Unsubscribed!');
}, 5000);
Many operators like take, first, and takeUntil automatically complete the Observable, which unsubscribes for you.

Common Operators Reference

Here are the most frequently used operators:

map

Transform each emitted value
pipe(map(x => x * 2))

filter

Only emit values that pass a test
pipe(filter(x => x > 10))

take

Take only the first N values
pipe(take(5))

debounceTime

Wait for silence before emitting
pipe(debounceTime(300))

switchMap

Switch to a new Observable, canceling previous
pipe(switchMap(id => ajax(`/api/${id}`)))

catchError

Handle errors gracefully
pipe(catchError(err => of(defaultValue)))

merge

Combine multiple Observables
merge(obs1$, obs2$, obs3$)

scan

Accumulate values over time
pipe(scan((acc, val) => acc + val, 0))

Next Steps

Core Concepts

Deep dive into Observables, Observers, and Subscriptions

Operators Guide

Master the full suite of RxJS operators

Subjects

Learn about multicasting with Subjects

Schedulers

Control timing and concurrency
Pro tip: The best way to learn RxJS is by building! Try recreating common UI patterns like autocomplete, infinite scroll, or drag-and-drop using Observables.