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.
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…?
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.
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).