RxJs — Advance input handling implementation

Gili Yaniv
4 min readMar 18, 2018

We all have it somewhere in our project, It may be in the navigation bar, may be is some kind of data list filter, but how well are you familiar with your text input?

Lately I found my self thinking what is the best way to use text input field to search from API or filter data. In the following post I’ll present my approach for handling this kind of scenario.

First of all I want to tackle this issue from the events witch trigger the operation handling.

Most of modern application don’t use an action button to perform the operation but subscribing to user type event and trigger actions accordingly.
For this we can use the Observable.fromEvent() and bind it to ‘keyup’.

But when we do that we are making our app to perform it’s operation on every note.

Taken from https://medium.com/aviabird/rxjs-reducing-number-of-api-calls-to-your-server-using-debouncetime-d71c209a4613

Imagine that you are doing a server request on every ‘keyup’ event, that’s doesn’t make any sence.

For that issue there’s a nice combo of two operators, debounceTime() and distinctUntilChanged().

To the debounceTime() we can pass time in milliseconds we are wishing to delay the operation. So debounceTime(3000) will make the observable to pause for 3 seconds before he continue. That’s nice but not useful for us because we are only deferring the action, not canceling it. So it will emit all actions 3 seconds after the ‘keyup’ occurred.

distinctUntilChanged() to the rescue!
This operator will make the emitter to be trigger only once. That’s been that it will buffer all of the ‘keyup’ event whiten the last 3 (As supplied in debounceTime(3000) ).

Your observable should be looking something like that:

And will be trigger only after 3 seconds from the last ‘keyup’ emit (while ignoring emits in between).

Taken from https://medium.com/aviabird/rxjs-reducing-number-of-api-calls-to-your-server-using-debouncetime-d71c209a4613

Can we make it event batter ? As my friend Obama once said, YES WE CAN!

When user focus out from an input field he probably done typing, so why should he wait for those extra 3 seconds for the operation to start? How about we will listen to both ‘keyup’ event and to ‘blur’ event and merge them into one listener?

Now we hold two streams of data, one for the ‘keyup’ and one for the ‘blur’.
Let’s talk about how to properly merge them together.

Can we use the concat()operator?

Concat operator digram

No, because we’ll be subscribe to the first observable and then to the second one(Not both of them together ). This is not the behavior we want.

How about merge()operator?

Merge operator digram

Well, that’s also not work for us..
merge() will perform both ‘keyup’ and ‘blur’ so every operation will be performed twice.

So what can we use?
Lets bring in the race() operator.

By ‘racing’ them together we can make only one observable to be subscribe, the first one that emit data. So, if the blur event happens before the 3 seconds debounce it will be subscribe for farther handling. If the 3 seconds debounce will trigger before the ‘blur’, this observable will be subscribe for farther handling.

Using race operator

That’s almost perfect but we still have one issue..
Because we subscribe only to the observable who “won” the race and emit’s first we are ignoring the one who lost.
That’s mean that if ‘blur’ occurred first no matter how many time you will try to type and wait for the debouce, it will be ignored. The same vice versa, If the ‘keyup’ debounce triggered first you will ignore the ‘blur’ event from now on.

Most of the time it’s not the desired behavior, We want the race to keep going.
So we can wrap our race with Observable.defer() and use the take(1) and repeat() operators.

Using race operator nested in defer one.

What we are doing here is the same as we are doing with the race() , But when we use Observable.defer() ,take(1) and repeat() we are telling the subscriber to resubscribe to the race observable after every ‘round’ (emit). Now both ‘keyup’ and ‘blur’ will work, one at a time and until you’ll manually unsubscribe from it!

I found this solution very useful in many cases. It reduce retardant operation and improve user experience.

The following stackblitz demonstrate the above code in an angular environment.

More about distinctUntilChanged() and debounceTime() you can read in Kartik Jagdale great post.

--

--