Angular Rxjs: Essential operators

Gustavo Lage
Webtips
Published in
5 min readJul 14, 2020

Every web developer knows the complexity of working with reactive programming, asynchronous codes, callbacks, and promises.

Luckily, Rxjs (Reactive Extensions Library for JavaScript) can allow us to write code more easily and concisely, and to handle our events in a more organized way.

Before going into depth with Rxjs, we must understand the concept of reactive programming and what lies behind it. Reactive programming means that an external event needs to happen for our code to be executed, as a reaction.

It all starts with the Observer pattern: an event-oriented design pattern, widely used on the web and which is the basis of reactive javascript programming. In this pattern, we have an object called Subject that is responsible for monitoring and detecting when an event happens, in order to notify observers of state changes.

Observables

Rxjs Observables encapsulate the observer pattern. There are many advantages to them:

⦁ They are reusable, unlike promises.

⦁ They allow you to better handle data streams: with a single Observable you can consume information from the server from time to time, be notified, and update state changes.

⦁ Their operators are functions that allow for the manipulation of data streams.

In this article, we are going to focus on some of these operators.

The example project uses Angular 9 and Rxjs 6. You can download it in my GitHub repository here.

Mock backend

We will use json-server to create a prototype for our backend.

In the directory, ‘backend’ is the project that we will use to simulate your API. The db.json file represents the database containing the products that we will access.

Now we are going to run our API. Accessing the project directory, execute the npm start command. The API will be initialized on the http://localhost:3001 port.

Incremental search

In this example, we will perform a product search in the backend every time the user types something in the search field. Here are some of the operators we will use:

debounceTime: We will use this operator to control data entry in the search field. We will create a delay to allow for the typing of more characters, thus avoiding requesting each key typed.

distinctUntilChanged: We will use the operator to avoid making an unnecessary request if the user deletes a character and types again quickly.

filter: We will use this operator to filter the typed content and allow the request to be made only if the search contains more than two characters.

tap: We will use the tap operator to perform actions before sending the request.

switchMap: We will use this to cancel the previous observable in case a new request occurs.

Ex: If the user types the word “not”, a request will be made to search for products that contain “not”. If, before the response arrives the user enters one more letter to form the word “note”, switchMap will cancel the Observable “not” and create a new Observable with the request “note”.

Preparing the search

We will create an Observable to receive the values that were entered. For that, we will use the Subject, a special type of Observable that can send notifications to multiple subscriptions through a single source.

Each time the user presses a key, it immediately triggers the event through the next() method.

Our HTML looks like this:

Retry errors

Rxjs provides us with some operators to facilitate error handling. In this example, we will call a request that will succeed and another that will fail. When the failure occurs we will use the retryWhen operator to make three new attempts.

retryWhen allows us to make new attempts based on specific criteria.

interval: we will use this operator to create an Observable during a time interval. For example, using 1000 as a parameter will instruct it to emit every second. We will use the interval to create two Observables: the first one to request our API products successfully, the second to create an error.

const request = interval(1000);

As we are only interested in the first two emissions created by the interval operator, we will use take. Otherwise, we would create Observables indefinitely.

take(2),
map(index => {
if (index === 0){
return this.productService.getProducts();
}
return this.productService.getProductsError().pipe

In the above code, we also used map to manipulate the observable emitted by the interval operator, where “index” represents a numeric value.

Below, we also used the take operator to emit just three observables that represent our retry attempts. We’ll use the operator tap to create our logs for every attempt.

retryWhen(err => interval(1000).pipe
(
take(3),
tap((a) =>
{
this.logs.push(`Retry attempt ${a + 1}: At ${new Date().toISOString()}.`);
})
)

Combining results

In the next example, we will be working with simultaneous requests.

Parallel requests

In the first example, we will use the forkJoin operator to create three requests. Each will be responsible for returning a product from a category. We will subscribe only when the response to the three requests is complete.

Here, map will assist us in handling the data returned by our Observable. Our getProductsByDepartment method returns an array of products, but we just want to retrieve a single product from each department. map will be responsible for doing the treatment and returning the first product of the list.

Sequential requests

In the second example, we will use the concatMap operator to create requests sequentially: the next Observable will be created only when the previous one is complete.

I also used the delay operator to wait a second until the next request.

After the last request has been completed, we can just subscribe.

firstHomeProduct.subscribe(resp => 
{
console.log('Home Product: ', resp);
this.products.push(resp);
});

Conclusion

Rxjs is an extensive and versatile library, with more than 100 operators to help with data transformation and error handling. Asynchronous codes are not always easy, and it is normal to encounter difficulties along the way. But understanding the way Observables handle asynchronous calls and returns is half the work. After that, what is left is understanding what kind of problem you need to solve in order to know how to organize your code and define which operators to use.

--

--