Typeahead

Now that a Pig Latin translator is under your belt, let’s tackle a different problem: typeahead. A typeahead system is one where, as the user types, the UI suggests possible options. In other words, the UI types ahead of the user. In the screenshot, typeahead helps to select a U.S. state. The user entered ar and the UI suggests all states that might fit ar, in alphabetical order.

images/bootstrap-typeahead.png

There has been much gnashing of teeth and pulling of hair over typeaheads. Before observables, collecting the stream of keypress events and parsing out possible results was difficult to pull off and filled with with race conditions. Don’t just take my word for it; read through this imperative typeahead implementation to see whether you can find the bug:

 myInput.addEventListener(​'keyup'​, e => {
 let​ text = e.target.value;
 if​ (text.length < 2) { ​return​; }
 let​ results = [];
 for​ (​let​ i = 0; i < options.length; i++) {
 if​ (options[i].includes(text)) {
  results.push(options[i]);
  }
  }
  resultEl.innerHTML = ​''​;
 for​ (​let​ i = 0; i < results.length; i++) {
  resultEl.innerHTML += ​'<br>'​ + results;
  }
 });

The deliberately inserted bug is in the last for loop. The code appends the entire results array to resultEl several times, instead of each element being added individually, which results in a conflagration of misparsed JSON, instead of a nice, orderly list. This sort of bug is hard to find when reviewing imperative code, becauses variables aren’t isolated into single units of functionality. Already there are problems with the imperative code, and we’re not even talking about asynchronous requests yet. Imagine how much more complicated this code would get in a situation like Netflix, where the full database of potential results is too big to store on the client side. Each keystroke would trigger a new AJAX request. You’ll figure out how to tackle that in Chapter 4, Advanced Async. For now, think about how you’d build something like this with Rx.

All done? Here’s my implementation:

 import​ { fromEvent, ​from​ } ​from​ ​'rxjs'​;
 import​ { map, filter, tap, mergeMap, reduce} ​from​ ​'rxjs/operators'​;
 
 fromEvent<any>(typeaheadInput, ​'keyup'​)
 .pipe(
  map((e): string => e.target.value.toLowerCase()),
  tap(() => typeaheadContainer.innerHTML = ​''​),
  filter(val => val.length > 2),
  mergeMap(val =>
 from​(usStates)
  .pipe(
  filter(state => state.includes(val)),
  map(state => state.split(val).join(​'<b>'​ + val + ​'</b>'​)),
  reduce((prev: any, state) => prev.concat(state), [])
  )
  )
 )
 .subscribe(
  (stateList: string[]) => typeaheadContainer.innerHTML += ​'<br>'
  + stateList.join(​'<br>'​)
 );

The first line is familiar. Every keyup event emitted from the myInput element sends a new event object down the stream. The map operator takes that event object and plucks out the current value of the input (a string). This string is passed on to a new operator: filter. This operator works much like its namesake from arrays: It passes each datum into its function. If that function returns a truthy value, filter sends the datum down the line. If instead, the function returns a falsey value, filter does nothing—the value is not passed on. This filter in particular allows only values that are longer than two characters.

Now that the code is certain it has a value worth investigating, a tap operator clears the output element. Interacting with the web page is another way to use tap; at this point, the code does not care about what the value is, only that it was emitted. In these cases, tap is used to trigger a side effect (changing something outside of the immediate operator). In this case, the side effect clears the results area.

The final outer operator follows the mergeMap pattern above. The inner observable is made of the list of states (declared outside the snippet). Another filter selects only the states with a name that contains the current value of the input, using the ES6 includes operator. A map then bolds the instances of the current query, before a reduce collects them back into an observable. Finally, the subscribe call takes that list of states and adds them to the typeahead container element.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.74.66