I recently came across MkDocs-Material by Martin Donath, a fantastic open-source project with over 22k GitHub stars.
It’s an incredible contribution to the community, making documentation hosting effortless.
While exploring it, I got curious about how such a large project achieves reactiveness.
The stack is mostly HTML, SCSS, Preact, RxJS, and a few workers, and I saw this as the perfect opportunity to dive into RxJS—especially how it utilizes Observables and other advanced patterns.
RxJS observables come in two flavors: hot and cold.
Understanding the difference between them is crucial when working with reactive programming, as it affects how data is produced and shared among subscribers.
In this article, we’ll break down the differences with examples and show how to work with each type effectively.
Cold Observables: Fresh Data for Every Subscriber
A cold observable is one where the underlying data is created inside the observable.
This means that each subscription starts a new execution of the observable, producing unique values for each subscriber.
Example of a Cold Observable
import { Observable } from 'rxjs';
const coldObservable = new Observable(observer => {
const randomNum = Math.random(); // Generates a new number per subscription
observer.next(randomNum);
observer.complete();
});
coldObservable.subscribe(value => console.log('Subscriber 1:', value));
coldObservable.subscribe(value => console.log('Subscriber 2:', value));
// Output:
// Subscriber 1: 0.645732
// Subscriber 2: 0.927384
Each subscriber gets a different random number because the observable generates a new value each time someone subscribes.
Hot Observables: Shared Data Among Subscribers
A hot observable is one where the data is generated outside the observable.
All subscribers receive the same data and share execution, preventing redundant computations.
Example of a Hot Observable
import { Observable } from 'rxjs';
const sharedRandomNum = Math.random();
const hotObservable = new Observable(observer => {
observer.next(sharedRandomNum);
observer.complete();
});
hotObservable.subscribe(value => console.log('Subscriber 1:', value));
hotObservable.subscribe(value => console.log('Subscriber 2:', value));
// Output:
// Subscriber 1: 0.645732
// Subscriber 2: 0.645732
Since the random number is generated before the observable is created, all subscribers receive the same value.
Making a Cold Observable Hot with publish()
Instead of generating data externally, we can use the publish()
operator to convert a cold observable into a hot one.
import { Observable } from 'rxjs';
import { publish } from 'rxjs/operators';
const coldObservable = new Observable(observer => {
const randomNum = Math.random();
observer.next(randomNum);
observer.complete();
});
const hotObservable = coldObservable.pipe(publish());
hotObservable.subscribe(value => console.log('Subscriber 1:', value));
hotObservable.subscribe(value => console.log('Subscriber 2:', value));
hotObservable.connect(); // Ensures the observable emits shared values
// Output:
// Subscriber 1: 0.845291
// Subscriber 2: 0.845291
Calling connect()
ensures that the observable emits data only once, making it behave like a hot observable.
Completing Observables & Avoiding Memory Leaks
Observables should be properly completed to avoid potential memory leaks, especially when dealing with continuous streams.
Example: Completing an Observable Automatically
Using finally()
, we can detect when an observable completes:
import { timer } from 'rxjs';
import { finalize } from 'rxjs/operators';
timer(1000).pipe(
finalize(() => console.log('Observable completed'))
).subscribe(() => console.log('Emitted value'));
Example: Manually Unsubscribing
For infinite observables like interval()
, we must manually unsubscribe to prevent leaks:
import { interval } from 'rxjs';
const subscription = interval(1000).subscribe(value => console.log(value));
setTimeout(() => {
subscription.unsubscribe();
console.log('Unsubscribed from observable');
}, 3000);
After 3 seconds, the subscription stops receiving values, freeing resources.
Conclusion
- Cold observables generate new data for each subscriber.
- Hot observables share data among subscribers.
- You can convert a cold observable into a hot one using
publish()
andconnect()
. - Always handle completion and unsubscribing to prevent memory leaks.
Understanding these concepts will help you build efficient and scalable reactive applications using RxJS!
I’ll be sharing more learnings, so stick around/follow for more deep dives into RxJS and beyond! 🚀
While exploring mkdocs-material implementation, I've been learning how to adapt these techniques for LiveAPI, a product I've been passionately working on for quite a while.
With LiveAPI, you can quickly generate interactive API documentation that allows users to execute APIs directly from the browser.
If you’re tired of manually creating docs for your APIs, this tool might just make your life easier.
Top comments (2)
Very nice series of intros to Observables. If you'd like to help even more people understand, appreciate and use them effectively, including less experienced developers in your audience, you might want to try Rimmel.js in some of your examples. It's just another UI library but it makes Observables soooo much easier to use and make sense of than Preact, React, and the others...
Thanks a lot :)
I see, I'll try it out tonight.