Guides / Building Search UI / UI & UX patterns

Create an Autocomplete Search Experience with Angular InstantSearch

A common search pattern is to implement a search box with an autocomplete. Angular InstantSearch doesn’t come with a built-in Autocomplete widget but you can create your own using the connectAutocomplete connector to a generic Autocomplete component.

This guide shows you how to create a search box which displays an autocomplete menu linked to a results page.

The guide doesn’t cover the usage of the connector in a multi-index context. There is a dedicated section about that in the multi-index search guide. You can find the source code of both examples on GitHub.

Results page with Autocomplete

To implement this, use the Autocomplete component provided by Angular Material. Install and import it.

$
ng add @angular/material
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app.module.ts
import { NgAisModule } from 'angular-instantsearch';
import { MatInputModule, MatAutocompleteModule } from '@angular/material/autocomplete';
// make sure you have BrowserAnimationsModule
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@NgModule({
 declarations: [
   AppComponent,
 ],
 imports: [
   BrowserModule, BrowserAnimationsModule, NgAisModule.forRoot(),
   MatInputModule, MatAutocompleteModule,
 ],
 providers: [],
 bootstrap: [AppComponent],
})
export class AppModule {}

The next step is to create a custom Angular InstantSearch component AutocompleteComponent.

When connected to connectAutocomplete, the component exposes three props in the internal state:

  • query: the query string entered by the user,
  • indices: the list of suggestions,
  • refine: a function that takes a query string and retrieves relevant suggestions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { Component, Inject, forwardRef, Input, Output, EventEmitter } from '@angular/core';
import { BaseWidget, NgAisInstantSearch } from 'angular-instantsearch';
import { connectAutocomplete } from 'instantsearch.js/es/connectors';

@Component({
  selector: 'app-autocomplete',
  template: `
    <div>
      <input
        matInput
        [matAutocomplete]="auto"
        (keyup)="handleChange($event)"
        style="width: 100%; padding: 10px"
      />
      <mat-autocomplete
        #auto="matAutocomplete"
        style="margin-top: 30px; max-height: 600px"
      >
        <div *ngFor="let index of state.indices || []">
          <mat-option
            *ngFor="let option of index.hits"
            [value]="option.name"
            (click)="onQuerySuggestionClick.emit({ query: option.name })"
          >
            {{ option.name }}
          </mat-option>
        </div>
      </mat-autocomplete>
    </div>
  `
})
export class AutocompleteComponent extends BaseWidget {
  state: {
    query: string;
    refine: Function;
    indices: object[];
  };

  @Output() onQuerySuggestionClick = new EventEmitter<{ query: string }>();

  constructor(
    @Inject(forwardRef(() => NgAisInstantSearch))
    public instantSearchParent
  ) {
    super('AutocompleteComponent');
  }

  public handleChange($event: KeyboardEvent) {
    this.state.refine(($event.target as HTMLInputElement).value);
  }

  public ngOnInit() {
    this.createWidget(connectAutocomplete, {});
    super.ngOnInit();
  }
}

When you have finished your Autocomplete component, you can integrate it into your application. To do that, you must use two instances of the InstantSearch component. Two instances allow you to individually configure the number of hits and results retrieved by Autocomplete.

It’s also helpful not to have the query tied to both instances simultaneously so that you can clear the suggestions but still apply the query on the second instance.

With two instances you need a way to sync the query between the two. Use the ais-configure to do this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Component } from '@angular/core';
import algoliasearch from 'algoliasearch/lite';

const searchClient = algoliasearch(
  'YourApplicationID',
  'YourSearchOnlyAPIKey'
);

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  config = {
    indexName: 'demo_ecommerce',
    searchClient
  };

  public searchParameters = {
    query: ''
  };

  public setQuery({ query }: { query: string }) {
    this.searchParameters.query = query;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div class="ais-InstantSearch">
  <ais-instantsearch [config]="config">
    <div class="searchbox">
      <app-autocomplete (onQuerySuggestionClick)="setQuery($event)"></app-autocomplete>
    </div>
  </ais-instantsearch>
  <ais-instantsearch [config]="config">
    <div class="left-panel">
      <ais-current-refinements></ais-current-refinements>
      <h2>Brands</h2>
      <ais-refinement-list attribute="brand"></ais-refinement-list>
      <ais-configure [searchParameters]="{ hitsPerPage: 8 }"></ais-configure>
    </div>
    <div class="right-panel">
      <ais-configure [searchParameters]="searchParameters"></ais-configure>
      <ais-hits>
        <ng-template let-hits="hits">
          <ol class="ais-Hits-list">
            <li *ngFor="let hit of hits" class="ais-Hits-item">
              <!-- ... -->
            </li>
          </ol>
        </ng-template>
      </ais-hits>
      <ais-pagination></ais-pagination>
    </div>
  </ais-instantsearch>
</div>
Did you find this page helpful?