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.
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 usersindices
: the list of suggestionsrefine
: 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 app. To do that, you must use two instances of the InstantSearch component. Two instances let you 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 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 { liteClient as 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>