Understanding RxJS use cases (part II)
Tags: javascript, angular, rxjs
Published: 2022-04-27 11:12:58

_Original cover photo by [Altum Code](https://unsplash.com/@altumcode) on Unsplash._

# When to use operators?

In the first part of this series, we explored use cases of different RxJS functions used to combine `Observable` streams. 
In the second installment, we are going to take a look at different RxJS operators, understand how they work and in which scenarios 
are they applicable. 

As experience shows, we can have knowledge about the existence and functionality of certain operators, but sometimes it is
hard to spot that a certain problem can be solved by using a particular operator.

So, with that in mind, let's get started!

# Waiting for other `Observable`s

## debounceTime: wait for a quieter time

Perhaps one of the iconic use cases of an RxJS operator is `debounceTime`. `debounceTime` is an operator that allows us to wait until the emissions of an `Observable` have paused for a certain while, and only then emit the latest value. This allows us to prevent multiple handlings of the same event until the situation has settled. A great example of this is implementing an HTTP API call to search using the text being typed by the user. Of course, we would have to listen to the `input` event, but it makes no sense to make a call every time the user presses a key on their keyboard. To avoid this, we can use `debounceTime` to make the call when the user finished typing. Here is a small example:

```ts
const input = document.querySelector('input');
fromEvent(input, 'input').pipe(
    debounceTime(500), 
    // wait for 500ms until the user has finished typing
    switchMap(event => filterWithAPI(event.target.value)),
    // make the http call
).subscribe(result => {
    // handle the result
});
```

So this is one example of how time based operators can be used to make our app more efficient. Use this if you want to perform something only after a period of silence.

## auditTime: handle something only once in a while

`auditTime` is a specific operator, which, provided with a period of time, only emits the latest value once in that time frame. 
This may seem very specific, but we can come up with good use cases. Imagine the following scenario: we have an app that displays 
a graph of a stock exchange. We are connected with the server via a websocket, which provides us with real time data about the stock market. 
Now because the market can be volatile, we may get many emissions, especislly if we display several graphs, we might get multiple emissions in just several seconds. Now repainting the graph faster than every second can be a costly process (canvas can be memory heavy), and also might be confusing
for the end user. So, in this scenario, we might want to repaint the graph every one second. Here is how we can do it using `auditTime`:

```ts
observableWhichEmitsALot$.pipe(
    auditTime(3_000),
    // maybe other operators that perform costly operations
).subscribe(data => {
    // maybe a heavy repaint
});
```

So here we use `auditTime` for better and controlled performance.

## distinctUntilChanged: preventing unnecessary operations

We can improve the previous example even further, if, for example, our source might send data that is duplicate in a row. It does not even 
have to be completely different: sometimes we only care about some keys in the emitted object. In this case, we might want to prevent other heavy operations by using `distinctUntilChanged` with a specific condition:

```ts
observable$.pipe(
    distinctUntilChanged((prev, next) => {
        return (
            prev.someKey === next.someKey || 
            prev.otherKey === next.otherKey 
            // maybe some other conditions
        );
    }),
);
```

Now, paired with the `auditTime` from the previous example, we can use this to boost performance, aside from other use cases.

> If you only care about one key change, you might want to use [`distinctUntilKeyChanged`](https://rxjs.dev/api/operators/distinctUntilKeyChanged)

## timestamp: you need to display the time when data arrived

Imagine you have an application where you receive data continuously (maybe via a WebSocket), and display it as soon as it arrives.
But it is important for the user to know when the data has arrived (maybe when the message was received, for example). In this case, you might want to use the `timestamp` operator to attach the arrival time on a notification:

```ts
observable$.pipe(
    timestamp(),
).subscribe(({value, timestamp}) => {
    console.log(new Date(timestamp)); 
    // will log the datetime 
    // when the notification arrived in UTC 
});
```

This can also be used if we aggregate values (with the [`buffer`](https://rxjs.dev/api/operators/buffer) operator for example) to monitor the intensity of emissions (which times of day we receive most notifications, for example).

## toArray: you want to map lists of data

Lots of applications (especially in Angular) use `Observable`s instead of `Promise`s to handle HTTP calls. But sometimes we want to slightly modify the response before using it in the UI. And when the response value is an `Array`, it might become a bit messy from the
code perspective, if we want to map each item, but still emit an `Array`. Here is how `toArray`, in combination with `swichMap`, can help:

```ts
responseFromServer$.pipe(
    switchMap(response => response.data), 
    // switch to the data array, so that it emits each item
    map(item => {
        // here we can perform each mappings on each item of the array
    }),
    toArray(), // when the items are done,
               // collect all of them back to an array,
               // and emit it
); // now we have an Observable of an array of mapped items
```

## retry: handling errors when we deem necessary

Errors are a natural part of any application: whether the server failed to deliver a successful result, or there is some inconsistency
in our frontend app, we want to handle errors gracefully, and, if possible, still try to deliver the desired result to the user anyway.

One way to achieve this is using the `retry` operator; this operator will try to work the `Observable` (an HTTP request, for example)
again, as many times as we wish, until it succeeds. Here is an example:

```ts
responseFromServer$.pipe(
    retry(3), // try again 3 times
); // after 3 failed attempts, will finally fail and send an error
``` 

But what if we have a specific situation? For example, we show an error notification, and it contains a button the user clicks on to try again?
Now we can provide a config ro the `retry` operator to specify another `Observable` to wait for:

```ts
responseFromServer$.pipe(
    retry({
        count: 3, // we can also OPTIONALLY 
                  // provide how many times 
                  // a user is allowed to retry 
        delay: () => fromEvent(
              document.querySelector('#retryBtn'),
              'click',
        ), // wait for the user to click the button
    }),
);
```

Now, the `retry` operator will wait for the user to click the button, and will try again the `Observable` until it succeeds. 
This can become very useful specially in the case if we use Angular and some state management that provides for side effect management via RxJS, but can also be applicable in any other application.

> We can do the same with handling completed `Observable`s instead of error, for example, reopening closed WebSockets, using the [`repeat`](https://rxjs.dev/api/operators/repeat) operator with configs.

# What’s next?
In the second part, we examined use cases for operators that usually perform routine tasks, like error handling and data manipulation.
In the next and final article, we are going to examine use cases for operators and entities that accomplish more obscure, but still useful tasks, including `Schedulers`, `Subjects` and so on.