Frontend Development 11 min read

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.

ByteDance Dali Intelligent Technology Team
ByteDance Dali Intelligent Technology Team
ByteDance Dali Intelligent Technology Team
Understanding RxJS: Core Concepts, Operators, and Practical Implementations

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

Subject

Basic multicast support

BehaviorSubject

Caches the latest value and emits it immediately to new subscribers

ReplaySubject

Caches a configurable number of recent values

AsyncSubject

Emits 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

frontendJavaScriptReactive ProgrammingRxJSObservables
ByteDance Dali Intelligent Technology Team
Written by

ByteDance Dali Intelligent Technology Team

Technical practice sharing from the ByteDance Dali Intelligent Technology Team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.