Concepts / Building Search UI / Query suggestions
May. 10, 2019

Query Suggestions

Overview

In order to help users with their search, Algolia provides a feature called query suggestions. This feature creates an index with the best queries done by the users. You can then use this index to propose suggestions to your users as they are typing into the ais-search-box. The great thing with this feature is that once you’ve configured the generation of the index, it’s just about querying another index and you can easily use multi-index search to for that.

In this guide we will cover the use case where a search box displays a list of suggestions along with the associated categories. Once the user select a suggestion both the query and the category will be applied.

To build our list of suggestions we leverage the component ais-autocomplete. We won’t cover in too much details how to integrate an autocomplete with Vue InstantSearch. The autocomplete guide has already a dedicated section on that topic.

Refine your results with the suggestions

The first step of this guide is to setup our custom autocomplete component. To implement it we use the library Vue Autosuggest which provides a component to create an autocomplete menu. Once we have it, we have to wrap the component with the ais-autocomplete connector. You can find more information in the guide on autocomplete.

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
<template>
  <ais-instant-search
    :search-client="searchClient"
    index-name="instant_search_demo_query_suggestions"
  >
    <ais-configure :hitsPerPage="5" />
    <ais-autocomplete>
      <template slot-scope="{ currentRefinement, indices, refine }">
        <vue-autosuggest
          :suggestions="indicesToSuggestions(indices)"
          @selected="onSelect"
          :input-props="{
            style: 'width: 100%',
            onInputChange: refine,
            placeholder: 'Search here…',
          }"
        >
          <template slot-scope="{ suggestion }">
            <ais-highlight attribute="query" :hit="suggestion.item" />
          </template>
        </vue-autosuggest>
      </template>
    </ais-autocomplete>
  </ais-instant-search>
</template>

<script>
import { VueAutosuggest } from 'vue-autosuggest';

export default {
  components: { VueAutosuggest },
  methods: {
    onSelect(selected) {
      if (selected) {
        console.log('selected');
      }
    },
    indicesToSuggestions(indices) {
      return indices.map(({ hits }) => ({
        data: hits.map(hit => {
          return {
            ...hit,
            // this is for Vue AutoSuggest
            name: hit.query,
          };
        }),
      }));
    },
  },
};
</script>

Now that we have our autocomplete component we can create our multi-index search experience. The autocomplete targets the index that contains the suggestions. The rest of the widgets target the main index that holds our data. You can find more information in the autocomplete guide.

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<template>
  <div>
    <ais-instant-search
      :search-client="searchClient"
      index-name="instant_search_demo_query_suggestions"
    >
      <ais-configure :hitsPerPage="5" />
      <ais-autocomplete>
        <template slot-scope="{ currentRefinement, indices, refine }">
          <vue-autosuggest
            :suggestions="indicesToSuggestions(indices)"
            @selected="onSelect"
            :input-props="{
              style: 'width: 100%',
              onInputChange: refine,
              placeholder: 'Search here…',
            }"
          >
            <template slot-scope="{ suggestion }">
              <ais-highlight attribute="query" :hit="suggestion.item" />
            </template>
          </vue-autosuggest>
        </template>
      </ais-autocomplete>
    </ais-instant-search>
    <ais-instant-search
      :search-client="searchClient"
      index-name="instant_search"
    >
      <ais-configure :query="query" />
      <ais-hits>
        <div slot="item" slot-scope="{ item }">
          <ais-highlight :hit="item" attribute="name" />
          <strong>$ {{ item.price }}</strong> <img :src="item.image" />
        </div>
      </ais-hits>
      <ais-pagination />
    </ais-instant-search>
  </div>
</template>

<script>
import { VueAutosuggest } from 'vue-autosuggest';

export default {
  components: { VueAutosuggest },
  data() {
    return {
      query: '',
    };
  },
  methods: {
    onSelect(selected) {
      if (selected) {
        const { query } = selected.item;
        this.query = query;
      }
    },
    indicesToSuggestions(indices) {
      return indices.map(({ hits }) => ({
        data: hits.map(hit => {
          return {
            ...hit,
            // this is for Vue Autosuggest
            name: hit.query,
          };
        }),
      }));
    },
  },
};
</script>

That’s it! We have setup our autocomplete multi-index search experience. You should be able to select a suggestion from the autocomplete and use this suggestion to search into the main index.

A common pattern with an autocomplete of suggestions is to display the relevant categories along with the suggestions. Then when a user select a suggestion both the suggestion and the associated category are used to refine the search. For this example the relevant categories are stored on the suggestions records. We have to update our render function to display the categories with the suggestions. For simplicity and brevity of the code we assume that all suggestions have categories, but this is not the case in the actual dataset. Take a look at the complete example to see the actual implementation.

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
<template>
  <!-- ... -->
    <template slot-scope="{ suggestion }">
      <span>
        <ais-highlight attribute="query" :hit="suggestion.item" />
        <span>
          <em>
            in
            <span v-if="suggestion.item.category">
              {{ suggestion.item.category.value }}
            </span>
            <span v-else> All categories </span>
          </em>
        </span>
      </span>
    </template>
  <!-- ... -->
</template>

<script>
export default {
  methods:
    indicesToSuggestions(indices) {
      return indices.map(({ hits }) => ({
        data: hits.map(hit => {
          // categories is an array, but we only want the first element
          // hit.instant_search is undefined if there are no matched categories
          const [category] =
            (hit.instant_search &&
              hit.instant_search.facets.exact_matches.categories) ||
            [];

          return {
            ...hit,
            category,
            name: hit.query,
          };
        }),
      }));
    },
  },
};
</script>

Now that we are able to display the categories we can use them to refine the main search. We are gonna use the same strategy than the query. We’ll use the ais-configure widget with disjunctiveFacets and disjunctiveFacetsRefinement. These two parameter are the same ones as internally used in a refinement list.

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
<template>
  <!-- ... -->
  <ais-instant-search
    :search-client="searchClient"
    index-name="instant_search"
  >
    <!-- optionally to display the selected category -->
    <ais-current-refinements />
    <ais-configure
      :query="query"
      :disjunctiveFacets="['categories']"
      :disjunctiveFacetsRefinements="disjunctiveFacetsRefinements"
    />
    <!-- ... -->
  </ais-instant-search>
</template>

<script>
export default {
  data() {
    return {
      query: '',
      disjunctiveFacetsRefinements: undefined,
    };
  },
  methods: {
    onSelect(selected) {
      if (selected) {
        const { query, category } = selected.item;
        this.query = query;
        this.disjunctiveFacetsRefinements = category && {
          categories: [category.value],
        };
      }
    },
  },
};
</script>

That’s it! Now when a suggestion is selected both the query and the category are applied to the main search.

Did you find this page helpful?