DEV Community

Cover image for Operators & Chill: Getting Started with RxJS - II
Athreya aka Maneshwar
Athreya aka Maneshwar

Posted on

Operators & Chill: Getting Started with RxJS - II

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.

I wrote an article on observables yesterday, check it out:

Now, let's dive deeper into the real game-changers: RxJS Operators.

1. map

The map operator is your go-to when you need to transform data.

Think of it like JavaScript's Array.map, but for Observables.

Example:

import { of } from 'rxjs';
import { map } from 'rxjs/operators';

const jsonStr = '{ "greetType": "Hi", "familyMember": "Mom" }';

of(jsonStr)
  .pipe(
    map(json => JSON.parse(json))
  )
  .subscribe(obj => {
    console.log(obj.greetType);   
    console.log(obj.familyMember);  
  });

// Output:
// Hi
// Mom
Enter fullscreen mode Exit fullscreen mode

Here, we're transforming a JSON string into a usable JavaScript object.

Perfect when handling API responses.

Image description

2. tap

tap lets you perform side effects without affecting the actual data stream.

It's great for debugging.

Example:

import { of } from 'rxjs';
import { tap, map } from 'rxjs/operators';

of('rxjs')
  .pipe(
    tap(value => console.log(`Original: ${value}`)),
    map(value => value.toUpperCase()),
    tap(value => console.log(`Transformed: ${value}`))
  )
  .subscribe();
Enter fullscreen mode Exit fullscreen mode

You’ll see both the original and transformed values in the console.

Super handy for peeking into the data flow.

Image description

3. filter

filter does exactly what you think: it filters data based on a condition.

Example:

import { from } from 'rxjs';
import { filter } from 'rxjs/operators';

from([1, 2, 3, 4, 5])
  .pipe(
    filter(num => num % 2 === 0)
  )
  .subscribe(console.log);  

// Output:
// 2, 4
Enter fullscreen mode Exit fullscreen mode

Only even numbers make it through.

It's like the bouncer of your data stream.

4. debounceTime & throttleTime

Both operators control the rate of emitted values but behave differently:

  • debounceTime emits the last value after a delay.
  • throttleTime emits the first value, then ignores subsequent values for the set time.

Example:

import { fromEvent } from 'rxjs';
import { debounceTime, throttleTime } from 'rxjs/operators';

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

fromEvent(input, 'input')
  .pipe(
    debounceTime(500)
  )
  .subscribe(() => console.log('Debounced input:', input.value));

fromEvent(input, 'input')
  .pipe(
    throttleTime(1000)
  )
  .subscribe(() => console.log('Throttled input:', input.value));
Enter fullscreen mode Exit fullscreen mode

Try typing fast to see the difference.

debounceTime waits until you pause; throttleTime logs intermittently.

5. scan

scan accumulates values over time, similar to reduce in JavaScript.

Example:

import { fromEvent } from 'rxjs';
import { map, scan } from 'rxjs/operators';

fromEvent(document, 'click')
  .pipe(
    map(() => 1),
    scan((acc, curr) => acc + curr, 0)
  )
  .subscribe(count => console.log(`Total clicks: ${count}`));
Enter fullscreen mode Exit fullscreen mode

Every click increments the total count.

Simple yet powerful for cumulative tasks.

6. switchMap

switchMap is ideal when you need to cancel previous requests and switch to a new one.

Example:

import { fromEvent, interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';

fromEvent(document, 'click')
  .pipe(
    switchMap(() => interval(1000))
  )
  .subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

Clicking resets the interval.

Perfect for scenarios like search suggestions or live data feeds.

7. takeUntil

takeUntil stops emissions when another Observable emits a value.

Example:

import { interval, timer } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

const source$ = interval(500);
const stopper$ = timer(3000);

source$
  .pipe(
    takeUntil(stopper$)
  )
  .subscribe({
    next: console.log,
    complete: () => console.log('Completed!')
  });
Enter fullscreen mode Exit fullscreen mode

The interval runs until the timer fires after 3 seconds, then completes automatically.

8. takeWhile

takeWhile emits values as long as a condition is true.

Example:

import { from } from 'rxjs';
import { takeWhile } from 'rxjs/operators';

from(["Alice", "Bob", "Charlie", "Doug", "Eve"])
  .pipe(
    takeWhile(name => name !== "Doug")
  )
  .subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

It stops emitting once it hits "Doug".

Great for conditional data flows.

Wrapping Up

RxJS operators are the real magic behind reactive programming.

They help you transform, filter, combine, and control data streams effortlessly.

Experiment with these operators, combine them, and soon you'll be orchestrating complex async workflows like a pro.

Image description

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.

Image description

If you’re tired of manually creating docs for your APIs, this tool might just make your life easier.

Top comments (0)