Making a Typeahead search in RxJs
I am the king of procrastination
I recently gave a talk at LibertyJS on RxJS and some of its applications.
An hour from my scheduled time, I was busy hyping myself up when I realized I didn’t have a live code example, and even worse, the internet was down!
I quickly rushed to the Starbucks around the corner and tried to come up with a quick example of the power that RxJS provides, and some of the key operators. So I went to learnrxjs.io to look at some of their examples.
I found the typeahead example and quickly tried to analyze the code they had. Using their example as a foundation, I was able to write up a quick and easy search for heroes in the popular MOBA, DoTA2.
Let’s jump in.

Getting the data
First, we need a data set to search on. To keep things relevant, I went the route of creating an observable from a fetch request:
var getDataObservable = fromFetch( ‘https://api.opendota.com/api/heroes');The URL returns an array of objects that look like this:
{
“id”: 1,
“name”: “npc_dota_hero_antimage”,
“localized_name”: “Anti-Mage”,
“primary_attr”: “agi”,
“attack_type”: “Melee”,
“roles”: [
“Carry”,
“Escape”,
“Nuker”
],
“legs”: 2
}Since we only care about the localized name, let’s throw an operator on that observable to only emit the value we care about. Lets pipe into our observable:
getDataObservable.pipe(
map(val => {
return val.map(data => data.localized_name);
});The following code now returns an observable that will emit an array of values. When the request completes, we will now get an emission that looks like this:
[ “Witch Doctor”, “Anti-Mage”, “Lone Druid”, “Axe”, … ]Now that we have our dataset, let’s build out the HTML we’ll use to search for the heroes, as well as display them:
(I used twitter-bootstrap to make the HTML at least a little pleasing to look at. This is the reason for the “card” and “card-body” class names)
<div class=”card”>
<div class=”card-body”>
<input type=”text” id=”search-input” style=”width:25em;”>
</div>
</div>
<div id=”output”>
</div>We need to listen to the input field as it changes, then grab the value to search on. The fromEvent() creation operator is perfect for this:
var searchKeyUpObservable = fromEvent(document.getElementById(‘search-input’), ‘keyup’)We’ll come back to this observable soon.
To keep this example practical, we need a mock function that will act like an observable listening to an HTTP call:
function filterArrays(searchKey) {
return of(data.filter(hero => hero.indexOf(searchKey) > -1)).pipe(
delay(100)
);
}In this function, we are creating an observable from the array returned by the filter() function. Then we pipe into this new observable and, to mock the response time of an HTTP request, we delay the emission by 100 milliseconds.
Now that that’s out of the way, let’s return to our observable listening to the input field.
This observable will emit an event object, every time the input field gets keyed up. The emission is pretty raw, lets pipe into the observable and refine the data to only emit what we want.
Since this is a typeahead we want to give the user some time to type out their search before we perform a potentially costly operation. To do this we can add the debounceTime() operator:
searchKeyUpObservable.pipe(
debounceTime(300)
);The debounceTime() operator will only emit values if it was emitted 300 milliseconds after the previous.
Next, we want to emit the value of the input, not the raw event. Let’s add a map() operator:
searchKeyUpObservable.pipe(
debounceTime(300),
map(e => e.target.value)
);The map() above will get the event emitted, get the target element (our input field), then get the current value of the field.
Now we only want to emit values that are different than the previous value, this way we can save on resources:
searchKeyUpObservable.pipe(
debounceTime(300),
map(e => e.target.value),
distinctUntilChanged()
);The distinctUntilChanged() operator will only emit when the current value is different from the previous.
Next we will implement our filterArray() function. Because filterArray() returns an observable, we will have to subscribe to both that (inner) observable, and the (outer) searchKeyUpObservable. To do this, we will use the switchMap() operator:
searchKeyUpObservable.pipe(
debounceTime(300),
map(e => e.target.value),
distinctUntilChanged(),
switchMap(keyTerm => filterArrays(keyTerm))
);(switchMap() is pretty hard to wrap your mind around, so head here for a deeper explanation)
Finally, we want to update our output element with the new values:
searchKeyUpObservable.pipe(
debounceTime(300),
map(e => e.target.value),
distinctUntilChanged(),
switchMap(keyTerm => filterArrays(keyTerm)),
tap(c => (document.getElementById('output').innerText =
c.join('\n')))
);The tap() operator will let us perform an action on each emission, without changing what gets emitted. Here we use this to add the hero name emitted from our observable to our HTML.
Congrats! You now have a (semi) working type-ahead search!
