DEV Community

Jasmine Mirabueno
Jasmine Mirabueno

Posted on • Updated on

RxJS 101: Simple Analogy for RxJS Concepts

Hey there, fellow devs! Today I will be talking about something that I am finally able to tick off my to-do list just recently -- understanding RxJS concepts!

Ever since I started my journey as an Angular Developer, RxJS has been that topic which I can't wrap my head around. I know how to use it (maybe not correctly). I also know how to make it work. But if someone asked me what an Observable is and what the subscribe function does, I'll probably say something like, "An observable is the return type for HTTP calls, and you subscribe to it to retrieve the data." It's not technically wrong in Angular's perspective, but it's shallow, and it doesn't explain the whole idea correctly.

Now, I will try to explain the main concepts of RxJS and how to use it. My attempt will be divided into three parts:

Note that RxJS is a huge topic with complex concepts. This post is created to understand the foundation of RxJS: Observables, Observers and Subscribers (and also a bit of Operators). If you're trying to learn the basics, this article is for you.

Let's start!

Analogy Explanation

The formal definition in the RxJS website is difficult to digest, so here's an analogy to facilitate our understanding.

Sushi Train GIF

RxJS is like a Sushi train restaurant.

The restaurant has the following routine:

  1. The chef prepares the ingredients needed for the items in the Restaurant Menu, which creates the basic food item (ie. Sushi).

  2. A customer enters the restaurant, gets a table, selects the Order in the kiosk and submits it.

  3. A chef will receive the order items and will proceed to its preparation. Depending on the order, he might add a drink or some side dishes with the sushi, according to the customer's request.

  4. Once the chef finishes preparing the order, a Restaurant Staff will then handle the delivery of the order through the sushi train. The order will be complete when the train successfully delivers the order to the correct table. In case the staff committed a mistake in specifying the destination of the order, the Restaurant staff will then handle the order to be delivered to the correct table.

  5. Just in case the customer decides to eat somewhere else, canceling orders are allowed.

The events mentioned above are what the Sushi Train Restaurant is built upon. Without these processes, the restaurant cannot function.

And now you may wonder, how does this relate to RxJS?

  1. The Restaurant Menu represents an Observable. It is an idea of an invocable collection. None of the items on the menu are given yet to the customer, but it is ready to be delivered or invoked.

  2. The Order represents a Subscription. It is the execution of the Menu Item, which represents the Observable. The Menu Item will stay in the kitchen unless a customer orders it. In the same way, an Observable will only be executed if it is subscribed upon.

  3. The process of assembling the order represents the Operators. A Menu Item can be transformed and combined with other menu items before it is delivered as an order. RxJS Operators are functions that do the same to a collection of data or events.

  4. The Restaurant Staff is the Observer. He literally observes the menu items that were prepared and knows what to do when the next item arrives, when an error occurs during delivery and when the item is successfully completed. An Observer listens to the values of the Observable and handles it according to its collection of callbacks (next, error, complete).

  5. And lastly, the cancellation of order represents the unsubscription.

RxJS is like a Sushi Train Restaurant. In its core is the Restaurant Menu (Observable), while other elements such as Orders (Subscription), Restaurant Staff (Observer), Chef and Customers play their part to achieve an asynchronous and event-based process.

Code Example

By now you might be thinking, "Well, I finally get the concept, but I don't know how to apply it in my code!" Say no more, fam. I gotcha.

Let's look at this simple Observable that we created using the creation function of in RxJS.

    const restaurantMenu$ = of('Tuna Roll', 'Egg Roll', 'Salmon Roll');
    const order = restaurantMenu$
      .subscribe({
          next(item) { console.log('Order: ' + item); },
          error(err) { console.error('Failed to deliver order: ' + err); },
          complete() { console.log('Order Complete'); }
      });

    /** Code output: 
    Order: Tuna Roll
    Order: Egg Roll
    Order: Salmon Roll
    Order Complete
    **/

So what do we have here?

  • Observable is the restaurantMenu$
  • Subscription is the order
  • Observer is the object passed to the subscribe function. The Observer knows what to do when an item is emitted from the Observable, when it errors and completes.

In the output of the code above, we didn't see the 'Failed to deliver order: ' log because no error occurred. It is also important to note that a subscription can end in three ways:

  1. unsubscribe() - cancel the execution of the Observable
  2. complete() - stops the Observable and can only happen if there was no error
  3. error() - happens if an error occurred, stops the Observable (complete won't be triggered).

Let's say there was a mistake while delivering the second item, the Egg Roll? Will it still proceed to emit the third item (Salmon Roll)? The answer is no. The Observable stops when an error is encountered. That's why it is important to make sure you handle it in your code, but for the sake of simplicity, we won't discuss techniques to handle errors here.

So far, we have already shown the three concepts. How about Operators? How do we use it?

Let's say that for a day, the restaurant gives a free drink promo to all orders. How will we do that? Let's look at this code:

    const restaurantMenu$ = of('Tuna Roll', 'Egg Roll', 'Salmon Roll')
      .pipe(
        map(item => item + ' with Free Drink')
      );
    const order = restaurantMenu$
      .subscribe(orderItem => console.log('Order: ' + item ));

    /** Code output: 
    Order: Tuna Roll with Free Drink
    Order: Egg Roll with Free Drink
    Order: Salmon Roll with Free Drink
    **/

There are two things that changed. Let's talk about the $restaurantMenu$ Observable.

Since we wanted each item to come with a free drink, we will have to pipe through a set of operations so we can transform the item. We do this through Operators. In the code above, we used the map operator to transform each item with a free drink included.

There are actually two types of Operators. Pipeable Operators and Creation Operators. In the code shown above, we added a Pipeable Operator. This kind of operator is used through the syntax of observableInstance.pipe(operator()). Creation Operators are the other kind of operator, which can be called as standalone functions to create a new Observable. An example of this is the function of which we used in code.

Note that Observables are lazy. This means that when we created it through the of function with a pipe of operations, it will not be executed upon initialization. As I said earlier, it will only be executed when you subscribe to it. This is also one of its difference with Promises, which is executed as soon as it is defined.

Now, let's move on to the order Subscription.

Compared to our previous example, the Observer has been reduced to only one line: orderItem => console.log('Order: ' + item ) but it still worked. This is because the next() function is the only required function while error() and complete() is optional. If you only pass one function, it will be assumed as the next() callback function.

You can also see that in our result log, there is no 'Order Complete' log. Since we didn't define what the Observer should do when it completes, it did nothing upon completion.

We have tackled Observables, Observer, Subscription and Operators. Yay! But wait, isn't it weird that the order returns all the items in the restaurantMenu$? What if the customer only ordered a Salmon Roll? The Observable should only emit the said item.

Let's try to do that by using the filter operator.

    const restaurantMenu$ = of('Tuna Roll', 'Egg Roll', 'Salmon Roll')
      .pipe(
        map(item => item)
      );
    const order = restaurantMenu$
      .pipe(
        filter(item => item === 'Salmon Roll')
      )
      .subscribe(orderItem => console.log('Order: ' + orderItem));

    /** Code output: 
    Order: Salmon Roll
    **/

Here, we filtered the menu with the corresponding order item, 'Salmon Roll'. Normally you will filter it by ID since the name itself is possible to be transformed somewhere else and you won't have a match. But since this is a simple example and we only emit strings from the Observable instead of an object, we didn't use an ID filter.

That's it! We talked about the basic concepts in RxJS. Before we end this article, let's summarize what we learned.

Summary

  • RxJS is a library for composing asynchronous and event-based programs by using observable sequences. We can think of it as a Sushi Train Restaurant.
  • An Observable is a collection that is waiting to be invoked, with future values or events. Much like a Restaurant Menu.
  • A Subscription is an execution of an Observable, like an Order to a Menu item.
  • An Observer is a collection of callbacks that knows how to listen to values delivered by the Observable. These callbacks are the next(), error(), complete() methods. It works like a Restaurant staff which knows what to do with the orders.
  • Operators are functions that are used for manipulating the Observable. We can think of it as the process of assembling the order. There are two types: Pipeable and Creational.
  • An Observable can be stopped by unsubscribing or through the error() and complete() callbacks.
  • Observables are lazy, they don't execute until subscribed upon.

There you go. I hope this article helped you in understanding the basics of RxJS. Thanks for reading!

Top comments (0)