Handle Your Form Input Events with RxJS

March 02, 2022

Handle Your Form Input Events with RxJS

In this post I’ll demonstrate how you can leverage the power of RxJS to gain control over your reactive form input events. The Stackblitz for this post can be found here. Let’s get started.

DataMuse API

I’ll be using the datamuse api in this post.

App Service Setup

The app.service.ts will have one method, getWordsThatSoundLike, which takes one argument, the search term from, and it returns an RxJS observable of type IWordSearchResult array. You can find the interface for this in src/app/interfaces/words.ts.

getWordsThatSoundLike(searchTerm: string): Observable<IWordSearchResult[]> {
    return this.httpClient.get<IWordSearchResult[]>(`${this.soundsLikeEndpoint}${searchTerm}`);
  }

Add Input to Template

I’ll add an input to the app.component.html file.

<label for="searchInput">Search For Words That Sound Like: </label>
<input id="searchInput" type="text" [formControl]="searchInput" />

I’m using the ReactiveFormsModule, which means I need to create the searchInput form control in the app.component.ts file.

 searchInput = new FormControl('');

To listen for changes on the input I’ll subscribe to the valueChanges event on the searchInput form control in ngOnInit.

ngOnInit() {
   this.searchInput.valueChanges.subscribe((value: string) =>
     console.log(value)
   );
 }

I’m only console logging the value for now, but let’s go ahead and see it in action.

console log input search zoomed

Very cool, this seems to be working. What would happen if we replaced the console.log statement with the api call in the service?

Wire Up the API Call

Let’s replace the console.log with a call to the getWordsThatSoundLike method.

ngOnInit() {
    this.searchInput.valueChanges.subscribe((value: string) =>
      this.appService
        .getWordsThatSoundLike(value)
        .subscribe((res) => console.log(res))
    );
  }

What is going to happen now…?

console log datamuse first try

Every time I made a change to the input, a api call was made. This is a good way to really hammer on a server. This is a great use case for RxJS. We can leverage a few of the RxJS operators to help gain control over how many api calls we make and more.

I’m going to add a property to app.component.ts called wordsThatSoundLike. This property will be an obsvervable of type string array. We’ll assign the returned value from the this.searchInput.valueChanges event (which will be an observable of type string array).

wordsThatSoundLike!: Observable<string[]>;

And here’s the updated ngOnInit in the app.component.ts file. As you can see, there’s quite a few RxJS operators in use. RxJS is awesome because the operators are easily composed together.

 ngOnInit() {
    this.wordsThatSoundLike = this.searchInput.valueChanges.pipe(
      map((wordSearch: string) => wordSearch.trim()),
      debounceTime(250),
      distinctUntilChanged(),
      filter((wordSearch: string) => wordSearch !== ''),
      switchMap((wordSearch: string) =>
        this.appService.getWordsThatSoundLike(wordSearch).pipe(
          retry(3),
          startWith([]),
          map((resArr: IWordSearchResult[]) =>
            resArr.map((res: IWordSearchResult) => res.word)
          )
        )
      )
    );
  }

You’ll first see that we use map and pass in the search term in which we call the trim method. Next we use the debounceTime operator so that we don’t constantly make an api call every time the input changes. The debounceTime operator will emit a notification from the source observable only after the specified period of time (in milliseconds) has passed without an emission from the source observable. In our case, our api call will only run once the user has stopped typing for at least 250 milliseconds. Next, we use the distinctUntilChanged, which will compare the current emission to the previous, and if they are distinct from each other, will emit the notification. In our case, if a user somehow inputs the same word as they previously did, we won’t make an api call. We use filter to ensure we aren’t sending an empty value.

The switchMap operator kinda goes hand in hand with making get requests. This operator has a canceling effect. It will cancel its previous observable and subscribe to a new one. That’s a pretty significant difference from Promises. This means we aren’t going to fire off a billion api calls, we will instead cancel the inner observable and switch to a new one. Once inside, we make our api call, and use a few more RxJS operators. The retry operator is used just in case something happens with the call. The startWith operator really just tells us that we should start with an empty array, this will allow us to always know that we are working with an array, which means less grief when we try to iterate over the wordsThatSoundLike observable in the template. Finally, we use map again to ensure that we are only returning the word property.

Displaying the Values In the Template

In the template I’m using the async pipe since wordsThatSoundLike is an observable (I talked about the benefits of using the async pipe in a previous post).

<ul>
  <li *ngFor="let word of wordsThatSoundLike | async"> {{word}}</li>
</ul>

And here we can see our results being displayed.

console log datamuse after rxjs

Wrapping Up

RxJS is pretty amazing. I have recently gained more of an interest in the concept of reactive functional programming. I know that I glossed over quite a bit here, but I hope this is enough to motivate you to do tinker around with RxJS (the stackblitz link I shared is great for that).


Profile picture

Written by Jason Fritsche.

Designed and built by Jason Fritsche