Basic RxJS Course with Vanilla JavaScript
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 rxjsWith 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, BListening 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, 30That 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}`;
});Basic live search
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
tapfor quick debugging without affecting the stream.
9. Final project: live search
Put it all together in a live search mini app:
- Text input element.
- Listen with
fromEvent. - Apply
mapto extract the value anddebounceTimeto wait for typing to pause. - Request data with
ajax.getJSON. - Render results in the DOM.
- Handle errors with
catchError.
That is a complete reactive flow in one small project.
10. Resources and next steps
Official docs:
Exercises to practice:
- Stopwatch using
interval. - Counter with
+and-buttons. - Live search against a public API.
- 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.