"User is Typing" lean RXJS implementation

Viewed 126

I am currently working on implementing a "User is Typing" feature for a multi-user chat room. With my limited knowledge of RXJS I was able to produce the following snippet of code which meets the bare minimum for the "User is Typing" feature:

   this.messageForm.message.valueChanges
      .pipe(
        tap(() => this.convoService.isUserTyping(true)),
        debounceTime(500),
        map(() => this.convoService.isUserTyping(false))
      )
      .subscribe();

However this implementation is not ideal since it produces too many requests to the server. My goal is to limit requests to 2, and using RXJS operators create the following behaviors:

  1. Only emit a request on the first keypress
  2. Ignore all subsequent keypresses
  3. Wait an X amount of time after what may be the last keypress
  4. Reset the countdown if new keypress is introduced before the countdown expires
  5. Emit a request once the countdown expires
1 Answers

Here's a side-effect free way to achieve it:

const typing$ = this.messageForm.message.valueChanges.pipe(
  switchMap(() => concat(
    of(true),
    of(false).pipe(delay(500)),
  )),
  distinctUntilChanged(),
)

You can use that to execute your requests. If isUserTyping is a fire-and-forget, you can just do

typing$.subscribe(typing => this.convoService.isUserTyping(typing));

If it is an asynchronous request (and returning an observable) I would rather go with this:

typing$.pipe(
  concatMap(typing => this.convoService.isUserTyping(typing))
).subscribe();

That way you ensure to execute all requests in sequence, avoiding out-of-order issues in high-latency situations¹.


¹ If latency was an actual concern this would require even more work using buffering in order to instead avoid backpressuring the requests too much, but this is out of scope for this question anyway.

Related