Basic input autocomplete with rxjs
Tags: javascript, webdev, tutorial, rxjs
Published: 2021-03-21 15:57:00

Autocomplete input text is one of the crucial features in web applications to provide better user experience

In this quick tutorial, we try to build an autocomplete text input using rxjs library.

Let's first generate an empty npm package 
```javascript
npm init -y
```

And we will add 3 pages : 
* index.html 
* style.css 
* index.js 

[You find full project in my github repo](https://github.com/hssanbzlm/vanilla-rxjs-autocomplete)

This is **index.html**
```html
<html> 
<head>
    <title>rxjs autocomplete</title>
  </head>
  <body>
    <div class="container">
      <div class="flex-container">
        <input
          type="text"
          class="form-control"
          placeholder="Search"
          id="text-input"
        />
        <ul class="list-group"></ul>
      </div>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

```
And now let's work on **index.js** 

In this file, we will handle changes in text input and then send http request to the server and finally, we display results 

First we are going to mock an API to be able to send HTTP requests according to the text typed and receive responses with the help of [json-server](https://www.npmjs.com/package/json-server). 

```javascript
npm i json-server 
```

Then we create a **db.json** file with some data. 

```json
{
"Clubs":[
    {"name":"Everton","year":"1878"},
    {"name":"Fulham","year":"1879"},
    {"name":"Manchester city","year":"1880"},
    {"name":"Manchester united","year":"1878"},
    {"name":"Tottenham","year":"1882"},
    {"name":"Aston villa","year":"1874"},
    {"name":"Leeds united","year":"1919"}, 
    {"name":"Liverpool","year":"1892"},
    {"name":"Arsenal","year":"1886"}, 
    {"name":"Chelsea","year":"1905"}
]
}

```
In the **package.json** file, we add in the scripts tag this command 

```json 
    "server": "json-server db.json --port 3000"
```
we are allowed then to run the server with:
```
npm run server
```
and the server is ready to handle requests. 

Now let's move to the next part and install [rxjs](https://rxjs-dev.firebaseapp.com/).

```javascript
npm i rxjs 
``` 
We take now the text input to be able to add an event listener to this element and this through fromEvent function (from rxjs) that emits events of a specific type (keyup) coming from the given target in our case text input element(inputTxt). 

```javascript
import { fromEvent} from "rxjs";
let inputTxt = document.getElementById("text-input");
let textChange$ = fromEvent(inputTxt, "keyup");
``` 
Now we are listening to inputTxt, whatever key is typed an event will be fired. 

In autocomplete feature, usually, we don't send HTTP request for each character, in other words, we wait an amount of time to see if the user add another character to the input, also, we don't send similar requests in a row to get better performance. 
Therefore we are going to use the pipe function to apply the required functionality to the observable stream. 

**map:** returns an observable by applying a project function to each value emitted by the source observable. This allows us to retrieve from target event only text value


**debounceTime:** returns an observable that emits values after only a particular time span in ms has passed without another source emission.

**distinctUntilChanged:** returns an observable that emits values only if it is different from the previous one. 

**mergeMap:** returns an observable that emits values by applying a specific function, it is the combination between mergeAll and map. The function to be applied in our case is the HTTP request made using fetch API that takes URL endpoint input and sends back a JSON response. Fetch function is called only if the value is not falsy else an empty array will be sent back.

[Understanding map,mergeMap,switchMap,concatMap](https://luukgruijs.medium.com/understanding-rxjs-map-mergemap-switchmap-and-concatmap-833fc1fb09ff)

```javascript 
import {
  debounceTime,
  map,
  distinctUntilChanged,
  mergeMap,
} from "rxjs/operators";
import { fromEvent, of } from "rxjs";
```
 
```javascript 
let autoSuggest$ = textChange$.pipe(
  map((e) => e.target.value),
  debounceTime(1000),
  distinctUntilChanged(),
  mergeMap((value) =>
    value ? fetch(url + value).then((response) => response.json()) : of([])
  )
);
``` 
Now let's handle this event by subscribing to textChanges$ observable. 

```javascript
autoSuggest$.subscribe((v) => {
  let list = document.getElementsByClassName("list-group")[0];
  while (list.firstChild) {
    list.removeChild(list.firstChild);
  }
  for (let club of v) {
    let li = document.createElement("li");
    let textNode = document.createTextNode(club.name);
    li.setAttribute("class", "list-group-item list-group-item-primary");
    li.appendChild(textNode);
    list.appendChild(li);
  }
});
```
We removed first previous results, we created a li element, then we iterated through data and created a text node where club names will be displayed, this text node is added to the li element which is added also to the list element.

To be able to run this little project we need a bundling tool, one of the easiest to configure is [Parcel](https://parceljs.org/).

Then we install parcel. 
```javascript 
npm i parcel-bundler
```
and we added parcel command in script tag with project entry point which is **index.html**

```json
"dev": "parcel index.html"
```

Then we can run input autocomplete, and don't forget to launch our JSON server
```javascript
npm run server
npm run dev
```

[Full github repo](https://github.com/hssanbzlm/vanilla-rxjs-autocomplete)