RxJS in practice: how to make a typeahead with streams!

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
.

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

But first, housekeeping

Last time 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:


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:

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
. 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:

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:

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!

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:

//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

//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'); });

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
.

The Practical Developer稿源:The Practical Developer (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合编程 » RxJS in practice: how to make a typeahead with streams!

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录