DEV Community

Cover image for Autocomplete: um simples componente nativo
Guilherme Carvalho Lima
Guilherme Carvalho Lima

Posted on

Autocomplete: um simples componente nativo

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.

// 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>
// 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> com um id de igual valor ao atributo list em outro elemento de entrada.

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

Top comments (2)

Collapse
 
wagnerssouza profile image
Wagner Souza

Ótimo assunto, e que bom que o Datalist já está homologado e implementado nos navegadores mais atuais: caniuse.com/#feat=datalist.

Acho ótima essa abordagem, só fico com um receio, hoje, dependendo do estilo que o Ux/Designer definir, temos uma grande dificuldade no frontend em modificar os estilos de componentes padrões do navegador, mas claro, vale uma conversa com o time de criação e experiência do usuário para entrar em acordo que envolve layout/experiência e performance.

Muito legal o artigo e o exemplo dado. Legal ver essa evolução dos componentes padrões dos navegadores atenderem mais os problemas que temos no dia-a-dia.

Collapse
 
guilima profile image
Guilherme Carvalho Lima • Edited

Como não é permitido customizar o estilo do <datalist> seria necessário troca-lo por outro elemento, se comprometendo com os efeitos em mostrar/esconder lista e gravar o valor selecionado. Fazer a separação de responsabilidades seria um pouco mais complicado, mas ainda sim possível.