Zademy

Basic RxJS Course with Vanilla JavaScript

RxJS; JavaScript; ReactiveProgramming
words words

1. Reactive programming basics

Reactive programming is about working with data streams that change over time.

A mouse click is a stream. Input typing is a stream. An HTTP response is a stream. Instead of polling to check whether data is ready, your code subscribes and reacts when values arrive.

2. What is RxJS

RxJS is a JavaScript library for composing asynchronous and event-based programs using Observables. It gives you a toolkit for transforming, filtering, combining, and error-handling streams of values.

Installation

With npm:

npm install rxjs

With CDN:

<script src="https://unpkg.com/rxjs@7/dist/bundles/rxjs.umd.min.js"></script>

Project structure

Three files: index.html, app.js, and styles.css (optional). That is all you need to follow along.

3. Observables

An Observable is a data source that can emit zero, one, or many values over time. You subscribe to it, and it pushes values to you.

const { of, from, interval } = rxjs;

of(1, 2, 3).subscribe(console.log); // 1, 2, 3
from(["A", "B"]).subscribe(console.log); // A, B

Listening to DOM events with fromEvent

const { fromEvent } = rxjs;
const button = document.getElementById("btn");

const click$ = fromEvent(button, "click");
const sub = click$.subscribe(() => console.log("click"));

Subscribing and unsubscribing

const { interval } = rxjs;

const sub = interval(1000).subscribe(v => console.log("tick", v));
setTimeout(() => sub.unsubscribe(), 5000);

If you forget to unsubscribe from an infinite stream, it keeps running and leaking memory. This is the number one RxJS footgun.

4. Basic operators

Operators are functions that transform or filter the values flowing through an Observable. You compose them inside pipe():

const { of } = rxjs;
const { map, filter, take } = rxjs.operators;

of(1, 2, 3, 4, 5)
	.pipe(
		filter(n => n % 2 === 1),
		map(n => n * 10),
		take(2)
	)
	.subscribe(console.log); // 10, 30

That pipeline keeps odd numbers, multiplies each by 10, then takes the first two. The result is 10, 30.

Operators you will use most

  • Creation: of, from, interval, timer
  • Transformation: map, filter
  • Combination: merge, concat
  • Filtering: take, first, debounceTime

5. DOM event handling

Click counter

const { fromEvent } = rxjs;

const btn = document.getElementById("btn");
const output = document.getElementById("output");
let count = 0;

fromEvent(btn, "click").subscribe(() => {
	count += 1;
	output.textContent = `Clicks: ${count}`;
});

The classic RxJS demo: debounce input so you only search after the user stops typing for 300ms.

const { fromEvent } = rxjs;
const { map, debounceTime } = rxjs.operators;

const input = document.getElementById("search");

fromEvent(input, "input")
	.pipe(
		map(e => e.target.value.trim()),
		debounceTime(300)
	)
	.subscribe(term => console.log("Search:", term));

Without debounceTime, you would fire a search request on every keystroke. With it, you wait until the user pauses. That is the difference between hammering your API and being polite to it.

6. Async handling

Observables vs Promises

A Promise resolves once. An Observable can emit multiple values over time, and you can cancel it with unsubscribe(). That cancellation is something Promises simply cannot do.

API calls with RxJS ajax

const { ajax } = rxjs.ajax;

ajax
	.getJSON("https://jsonplaceholder.typicode.com/users")
	.subscribe({
		next: data => console.log("Users:", data),
		error: err => console.error("HTTP error:", err),
		complete: () => console.log("Completed")
	});

7. Error handling and completion

const { of } = rxjs;
const { catchError, retry } = rxjs.operators;
const { ajax } = rxjs.ajax;

ajax
	.getJSON("https://api.example.com/data")
	.pipe(
		retry(2),
		catchError(err => {
			console.error("Request failed:", err);
			return of([]);
		})
	)
	.subscribe({
		next: data => console.log(data),
		complete: () => console.log("Finished")
	});

retry(2) tries the request up to two more times before giving up. catchError lets you recover by returning a fallback Observable, here an empty array. complete fires when the stream finishes successfully.

8. Best practices

  • Always unsubscribe from infinite streams (interval, fromEvent). Memory leaks are silent.
  • Use pipe() to compose operators. Do not nest subscriptions.
  • Keep heavy logic out of subscribe. Put it in the pipe.
  • Use tap for quick debugging without affecting the stream.

Put it all together in a live search mini app:

  1. Text input element.
  2. Listen with fromEvent.
  3. Apply map to extract the value and debounceTime to wait for typing to pause.
  4. Request data with ajax.getJSON.
  5. Render results in the DOM.
  6. Handle errors with catchError.

That is a complete reactive flow in one small project.

10. Resources and next steps

Official docs:

Exercises to practice:

  1. Stopwatch using interval.
  2. Counter with + and - buttons.
  3. Live search against a public API.
  4. Request retries with retry.

Next step: learn switchMap, mergeMap, Subjects, and advanced stream composition. Those are the tools that unlock the real power of reactive programming.