Autocomplete: um simples componente nativo
Tags: html, javascript, angular
Published: 2020-07-03 15:54:36

Normalmente utilizamos elementos `<input>` para inserção de dados em formulários web. Já quando é preciso um controle maior utilizamos o `<select>`, assim podemos limitar o tipo de informação que será enviada.
```html
// exemplo input
<div class="field">
  <label class="label" for="name">Produto</label>
  <div class="control">
    <input class="input" type="text" id="name">
  </div>
</div>

// exemplo select
<div class="field">
  <label class="label" for="bank">Bancos</label>
  <div class="select" id="bank">
    <select>
      <option>Selecione</option>
      <option>ABC</option>
    </select>
  </div>
</div>
```
A dificuldade começa quando precisamos criar um elemento `<select>` filtrável. Muitas vezes temos esse tipo de situação quando queremos facilitar a seleção por uma quantidade muito grande de opções. Mesmo que as opções estejam ordenadas seria uma péssima experiência para o usuário ter que procurar entre mais de vinte, descendo pela barra de rolagem. Nessa situação o uso do `<select>` deixa de fazer sentido, pois para tornar uma lista de opções filtrável precisamos que exista um campo de `<input>` que permita digitação.

Podemos chegar no resultado esperado criando um `<input>` e logo abaixo um elemento de conteúdo estático (`<ul>` ou `<div>`) listando as opções. A partir da inserção de caracteres no campo de `<input>` podemos filtrar e mostrar apenas as opções que contenham parte do conteúdo digitado. Contudo também devemos nos preocupar em acrescentar funcionalidades inerentes ao `<select>`:
1. Quando o campo estiver em foco, mostrar opções
2. Quando o campo não estiver em foco, esconder opções
3. Quando clicar na opção desejada, preencher o campo de `<input>`

```html
// page.component.html
<div class="field">
  <input  class="input" type="text" formControlName="name"
    [class.is-danger]="name.invalid && (name.dirty || name.touched)"
    (blur)="isVisible(false)"
    (focus)="isVisible(true)">
  <div class="dropdown is-block" 
    [class.is-active]="suggestions.length > 0 && isVisible">
    <div class="dropdown-menu" role="menu">
      <div class="dropdown-content">
        <ng-container *ngFor="let suggestion of suggestions" >
          <a class="dropdown-item" (mousedown)="setValue(suggestion);">
              {{suggestion.name}}
          </a>
        </ng-container>
      </div>
    </div>
  </div>
</div>
```
Agora temos um componente de _autocomplete_ com o mesmo comportamento de um `<select>`, porém podemos observar a existência de uma sobrecarga de regras entre a lista e o `<input>`. O desvio de responsabilidade torna o componente pouco flexível e de difícil manutenção, uma vez que parte substancial do código não define o motivo real da criação desse componente: ler e listar as sugestões.

A melhor forma de mantermos esse componente simples, flexível e sem perder a essência do `<select>` seria criando a interação entre o `<input>` e a lista de forma desacoplado por meio de um identificador entre eles. Isso já  possível utilizando o elemento nativo [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist) com um `id` de igual valor ao atributo `list` em outro elemento de entrada.
```html
// autocomplete.component.html
<datalist [id]="id">
    <ng-container *ngFor="let suggestion of list">
        <option [ngValue]="suggestion">{{suggestion.name}</option>
    </ng-container>
</datalist>

// page.component.html
<div class="field">
    <label class="label" for="bank">Bancos</label>
    <div class="control">
        <input class="input" type="text" id="bank" 
            formControlName="bank" list="bank-autocomplete"
            [class.is-danger]="bank.invalid && (bank.dirty || bank.touched)">
    </div>
    <app-autocomplete [id]="'bank-autocomplete'" [list]="banks"></app-autocomplete>
</div>
```
Dessa forma construímos um componente com responsabilidade única que poderia ser estendido para receber uma lista dinâmica por requisição http, lista com sub-itens ou campo de entrada variado. O componente de _autocomplete_ ficaria responsável por apresentar a lista e nada mais.