RxJS in practice: how to make a typeahead with streams!
Tags: javascript, rxjs, tutorials
Published: 2017-08-19 18:42:17

---
title: "RxJS in practice: how to make a typeahead with streams!"
published: true
description: Part 2 of the rxjs articles, how to use it!
tags: javascript, rxjs, tutorials
---

##What we will do
You know when you type into the Twitter search bar and it tries to guess what you want to search for? Say you start typing "SammyI" and the first result is my twitter handle, [@SammyIs_Me](https://twitter.com/SammyIs_Me). 

That's what we'll make (except probably maybe not for Twitter).

####But first, housekeeping
[Last time](https://dev.to/sammyisa/what-is-rxjs-when-seen-from-a-mile-away) we talked about streams and actions we do on those streams, but I did not use the correct terminology there. The streams are called Observables, and I will refer to those as such in the future. 

##Starting code
Let's skip the basic HTML, just make something like so:
```html
<input type="text" 
       name="typeahead" 
       class="typeaheadInput" />
```
You can add some CSS to make it look nicer if you want. Next, the starting Javascript - a ~~stream~~ observable that sends new data at every change of the input text box, and a function that logs the input to the console:
```javascript
const inputStream$ = Rx.Observable
		.fromEvent(input, 'input')
		.map(e => e.target.value);

inputStream$.subscribe(text => console.log(text));
```

We are even 'sanitizing' the observable to only get the useful data from the event.

##Getting the search data
To get the search/suggestion data, we will use [Datamuse API](https://api.datamuse.com). We will use the suggestion endpoint to get some word suggestions, like so:
```
GET https://api.datamuse.com/sug?s=sammy
Response:
[{"word":"sammy","score":35841},
{"word":"sammy sosa","score":35639}, 
... ]
```
Let's add that the request to our `subscribe` of our observable, and we have:
```javascript
inputStream$.subscribe(text => {
	fetch(`https://api.datamuse.com/sug?s=${text}`)
	.then( resp => resp.json() )
	.then( resp => console.log(resp) )
});
```
Now we are showing to the console an array of all the suggestions from the API. We are not done, but you can see the final product from here! 

##Making the search data also an observable
We continuously getting a stream of data from datamuse, can't we just make that another stream to be consumed? Yes we can!

There are a few new, important concepts in this section to tackle, so make sure you get a good grasp on it before moving on.

First, we don't want to hit the datamuse endpoint *at every single stroke*. If we do, we will be getting recommendations for `h`, `he`, `hel`, `hell`, `hello` and we only need the recommendations for the `hello`. 

So, we will **debounce** the observable. Debouncing means 'wait until we haven't received a new event on the stream for x milliseconds, then get the latest item and that's the new item of the observable. So, in our example from before, after we stop typing for one second, only `hello` will be sent to the observable. Try it out, change the `inputStream$` observable from before:
```javascript
const inputStream$ = Rx.Observable
		.fromEvent(input, 'input')
		.map(e => e.target.value)
		.debounceTime(2000);
```
Type on the input box and then wait for two seconds. Keep an eye on the console.

Let's make the search results a new observable! 
```javascript
const suggestionsStream$ = inputStream$
    //Fetch the data
    .mergeMap( text => fetch(`https://api.datamuse.com/sug?s=${text}`) )
    //Get the response body as a json
    .mergeMap( resp => resp.json() )
    //Get the data we want from the body
    .map( wordList => wordList.map(item => item.word) );
```
**I promise I will get into `mergeMap` soon, but first I must ask you to just trust in it. If you are dealing with a promises, use `mergeMap` instead of `map`**
Now that we have an observable that gives us an array of suggestions, we put that array somewhere. 

Since this is getting a bit longer than I anticipated, we will just list the suggestions in a div somewhere:
```javascript
//We made a div of class 'suggestion' for this
const suggestions = document.querySelector('.suggestions');
suggestionsStream$.subscribe(words => {
	suggestions.innerText = words.join('\n');
});
```
Now try it out! Type something, wait two seconds, look at the results!

##Final Code
```html
<input type="text" 
    name="typeahead" 
    class="typeaheadInput" />
<div class="suggestions"></div>
<script>
//Get the suggestions div
const suggestions = document.querySelector('.suggestions');
//Get the input component
const input = document.querySelector('.typeaheadInput');

//Input stream
const inputStream$ = Rx.Observable
                .fromEvent(input, 'input')
                .map(e => e.target.value)
                .debounceTime(2000);

//Suggestions stream
const suggestionsStream$ = inputStream$
                .mergeMap( text => fetch(`https://api.datamuse.com/sug?s=${text}`) )
                .mergeMap( resp => resp.json() )
                .map( body => body.map(item => item.word) )

//Handle the stream
suggestionsStream$.subscribe(words => {
	suggestions.innerText = words.join('\n');
});
</script>
```
Next time we will explain what `mergeMap` is (probably a shorter one, it is so much more than a promise handler!) and we will dive into animations with RxJS! If you have any questions/corrections/suggestions/compliments you can reach me via Twitter [@SammyIs_Me](https://twitter.com/SammyIs_Me).