Take the relationship with your users to the next level — using rxjs stream in Angular

Elad Elram
7 min readJul 26, 2021
Photo by Jon Flobrant on Unsplash

Could you bear with me for a moment? I know I promised something in the title, but I want to take a little detour in the business world before getting to it.

As for front-end developers, we need to be close to our users. In different worlds, they are called customers. The world of customer service is constantly changing; let’s take a use case of a customer asking for support as an example.

If you are approaching a big company with a support request or complaint in the old method, you face the guys from customer service. They open a ticket to your request, and the ticket starts the company's flow until it is resolved. If you call customer service again, you hit another person, and they will open a new ticket if there is no need to close the old one.

Photo by Icons8 Team on Unsplash

Companies now understand that this approach looks a little cold and distant, especially in a world where we’re all connected, and everything is happening online. So, with the help of social networks, they introduced a new way to connect using a chat window. This way, the client fills in an open line to the company, which looks closer to the other connections they have with their friends and family.

We can use the approaches mentioned above to understand how to deal with some challenges we had when dealing with user input. Now I hear you code junkies screaming: “stop the metaphors! just show me the code!” so let’s get into it.

The events approach / opening a ticket

For this example, let's take a simple search input. We put a search box in our app; The user types a query, and we need to look for it in the server and present the results to the user.

Our code looks like this:

In this method, we usually listen to the input event and send a request to the server. At this point, our network tab in the dev tools looks like that:

We can see that we’re “opening a ticket” on every user input, sending every request to the server, and listening to every response.

If we want to stick with the events approach, we need to handle few things on our own:

  1. Debounce — we don’t want to send every click the user made to the server; we want to wait for a little and send the user's word and not every letter they type. Finding the right debounce time is some kind of art. We don’t want to annoy the user, and at the same time, we want to give enough time between calls.
  2. Race condition — we only need the response of the last call, but we will get them all, and we can’t control the order. It may look fine during development, but we always need to remember our users. They may not have a blazing fast development computer and sometimes not a good network connection, and things can get messy.

These are not hard issues to handle. We can develop debounce mechanisms on our own, a timer, and some extra variables to track it, and it’s not that complicated. We can also handle the race condition using a cancellation method to the previous HTTP call, again not too complicated, but requires other variables or observable in our code.

Let’s take our relationship with our users to the next level and convert the user input into a stream, and then the above issues are a lot easier to handle.

The open stream approach / open chat

Photo by Christian Wiediger on Unsplash

So let’s take the user input and convert it to stream. If we’re using a plain input HTML element, we can do it with rxjs fromEvent.

Our code will look like this:

Now when we have a stream, we can use all the goodies of rxjs. In our case, since we only need the response of the last API call - we can use switchMap. This operator is convenient in a race condition situation - whenever a new value enters the stream, and the old value hasn’t been resolved yet, it will cancel the old one and switch with it. If we didn't get a response from the previous call, it would be canceled, and a new call with the new value will start.

Now our network tab looks like this:

We only get the response from the last API call. We took the user input as a flow like we’re in an open conversation. We’re respoding to the user on what they asked now, not on what they asked earlier.

So we handled the race condition, and now we can easily handle the debounce using the debounceTime operator. This operator will wait until one of two things happens: either the debounceTime is ended, or a new value enters the stream. If the time has ended — move to the next step; if a new value has entered — restart the timer.

Since we don't need the debounce to handle the race condition, we can choose if we want to debounce (this is why it’s commented on the code above) and set the value to the minimum for a better user experience.

Did you notice what happened here? We handled two issues, and we only added one line of code for each. There is no need to add unnecessary class members, timers, and logic; we have it all in rxjs; we just need to stream.

Convert a button to a stream

Let’s take another example of how converting a user interaction to a stream can help us. This time we’re adding a search button next to our input element. The search on the server will start only when the user clicks on the search button.

We do not need a debounce since we’re not triggering the search while the user is typing. But we do need to handle a different scenario. You probably know those annoying users; if they are not getting a response immediately, they will click on the button again and again until something will happen on the screen or until the computer will start smoking 🔥. If we do not deal with those users, then every click will trigger a new call to the server, and we don’t want that.

So let's convert the button click event to a stream:

What do we have here?

  • In the first example, we converted an HTML element event into a stream using fromEvent. But we're not always using plain HTML elements. Sometimes, we use some wrapper component that emits an event. Maybe you are using one of those if you have some UI components library in your app. For this example, let's say we have a component that wraps the button and emits an event (buttonClick) with every click. Usually, we’re binding to the output in the HTML the same as we do with events. But if we’re getting a reference to the component directly (with @viewChild) — we can subscribe to the output to get a stream.
  • Next, we map the click event to return the query the user entered.
  • Here comes the magic — we use distinctUntilChanged, another cool operator of rxjs, to tell the stream to stop moving to the next step if the value hasn’t changed.
  • If the value has changed, we move on to the switchMap and send the call to the server. The chances of race conditions are decreased, but they still exist for users with quick fingers.

Now we got it all covered; we can let the user click their mind off. We’re ignoring multiple clicks if the value didn’t change. And if it changed and the server still didn't respond to the previous call, we cancel it and send a new one.

Conclusion

Converting user input to a stream is pretty easy and very handy when we need to handle the user’s request in the context of their previous requests. I think a stream is representing those interactions better than events. We’re in a conversation with our users and not with a Pavlovic mechanism of action and reaction. But most of all, it makes our code looks cleaner and smaller.

You can use this approach with other user interactions and find the rxjs operators that fit your need.

If you survive this far and if you think of other implementations, you can share them with us in a comment 💬.

--

--