Understanding RxJS: Core Concepts, Operators, and Practical Implementations
This article introduces RxJS, explains its reactive programming paradigm, core concepts such as Observables, Observers, Subjects, Operators and Schedulers, and demonstrates practical implementations like a draggable ball and toast notifications, providing code examples and best‑practice guidance for front‑end developers.
Introduction
The author, a front‑end developer with experience in Angular and React, shares why RxJS is valuable for handling complex state management in data‑driven UI applications.
1.1 Background
Programming paradigms are compared: imperative, functional, declarative, and reactive. Reactive programming is described as a declarative paradigm based on data streams, exemplified by the expression UI = f(state) in React.
1.2 Prerequisite Concepts
1.2.1 Stream
A stream is a sequence of data elements emitted over time, analogous to items on a conveyor belt. Streams can smooth the difference between synchronous and asynchronous data.
1.2.2 Observer Pattern
The observer pattern defines a one‑to‑many dependency so that when an object changes, all dependent observers are automatically notified.
Advantages: decouples UI from data, provides a stable messaging interface, supports broadcast communication.
Disadvantages: high cost for deep hierarchies, risk of circular dependencies, observers only see final state changes.
Core Concepts
2.1 Observable & Observer
Observable is a lazy push collection of multiple values. It can emit values synchronously or asynchronously.
Multiple values can be pushed.
Values are only emitted when the Observable is subscribed to.
Observer consumes values delivered by an Observable. The interface is:
interface Observer
{
/** Whether the subscription is closed */
closed?: boolean;
/** Called when a value is emitted */
next: (value: T) => void;
/** Called when an error occurs */
error: (err: any) => void;
/** Called when the stream completes */
complete: () => void;
}
// observable.subscribe(observer);2.2 Subscription
A Subscription represents a disposable resource, usually the execution of an Observable. It must be unsubscribed to release resources and avoid memory leaks.
const subscription = stream$.subscribe({
next: (val) => console.info,
error: console.error,
complete: () => console.info('completed'),
});
subscription.unsubscribe();2.3 Subject
A Subject behaves like both an Observable and an Observer, allowing multicasting to many observers. It provides next(v) , error(e) , and complete() methods.
Subject Type
Features
Illustration
SubjectBasic multicast support
BehaviorSubjectCaches the latest value and emits it immediately to new subscribers
ReplaySubjectCaches a configurable number of recent values
AsyncSubjectEmits only the final value when the source completes
2.4 Operator
Operators are pure functions that enable declarative composition of asynchronous code. RxJS ships with many operators grouped by category (combination, transformation, filtering, multicasting, error handling, utility, conditional, aggregation).
2.5 Scheduler
A Scheduler controls when a subscription starts and when notifications are delivered. Common schedulers include null (default), queueScheduler , asapScheduler , asyncScheduler , and animationFrameScheduler .
Simple Practices
3.1 Drag‑Ball Example
Three streams are created from mouse events: mousedown$ , mousemove$ , and mouseup$ . Using switchMap and takeUntil , a drag$ stream calculates the ball's position.
const mousedown$ = fromEvent
(ball, 'mousedown').pipe(
map(getMouseEventPos)
);
const mousemove$ = fromEvent
(document, 'mousemove').pipe(
map(getMouseEventPos)
);
const mouseup$ = fromEvent
(document, 'mouseup');
const drag$ = mousedown$.pipe(
switchMap(initialPos => {
const { top, left } = ball.getBoundingClientRect();
return mousemove$.pipe(
map(({ x, y }) => ({
top: y - initialPos.y + top,
left: x - initialPos.x + left
})),
takeUntil(mouseup$)
);
})
); drag$.subscribe(({ top, left }) => {
ball.style.top = `${top}px`;
ball.style.left = `${left}px`;
ball.style.bottom = '';
ball.style.right = '';
});3.2 Toast Management Example
A toast is shown for 3 seconds; if a new toast appears before the timeout, the timer resets and the old toast is replaced. The implementation uses switchMap , timer , concat , and finalize .
const click$ = fromEvent(document.getElementById('btn'), 'click');
const toast$ = click$.pipe(
switchMap(() => {
let hideByDuration = false;
const duration$ = timer(2000).pipe(
mapTo('hide by duration'),
tap(() => (hideByDuration = true))
);
return concat(of('show'), duration$).pipe(
finalize(() => {
if (!hideByDuration) {
console.log('hide by next');
}
})
);
})
);
toast$.subscribe(console.info);Conclusion
RxJS excels in flow control and coordinating multiple asynchronous operations, allowing developers to write concise and efficient code. Consider RxJS when selecting technologies for front‑end projects.
References
ReactiveX
RxJS Official Site
Learning RxJS Operators
Design Pattern Illustrated
ByteDance Dali Intelligent Technology Team
Technical practice sharing from the ByteDance Dali Intelligent Technology Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.