Guides / Solutions / Ecommerce / Search / Autocomplete

Autocomplete with Federated Search

A federated search offers search results, Query Suggestions, product categories, and other content from multiple indices in a single interface.

Federated search user interface

Before you begin

This tutorial builds on Autocomplete with Query Suggestions.

Combining different data sources

With different indices for different content types, you can optimize the relevance for each type. For example, blog posts might require a different ranking strategy than products. Federated search can search multiple indices simultaneously and show the results in the same interface.

You can combine data sources in four different ways:

Data sources Example
Two or more Algolia indices One index for Query Suggestions, another for products, and another for a blog. Each index has different data types, data structures, and ranking strategies.
One Algolia index with different filters You can apply filters to contextualize and get extra data sets from the same index. For example, if your index has both movies and books, you can query a single index twice: one query with a filter for “movies” and the second query for “books”.
One Algolia index and filter values from the same index In a “product” index, you can have a set of category filters. You can apply these category filters and return relevant category values.
One Algolia index and third-party data You can search both an Algolia index and a third-party API asynchronously. For example, you can search into an Algolia index of restaurants and get a list of nearby addresses using the Google Places API.

You can add multiple data sources to Autocomplete with getSources. In this tutorial, Autocomplete uses these data sources:

  • Query Suggestions from the index instant_search_demo_query_suggestions
  • Product catalog from the index instant_search
  • Hierarchical product categories from instant_search

Add Query Suggestions as first data source

Follow the steps in Adding Query Suggestions to set up autocomplete-plugin-query-suggestions.

Add search results for products as second data source

First, import getAlgoliaResults from the autocomplete-js package to get results from an Algolia index.

1
2
import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js';
...

Next, use getSources and getAlgoliaResults to get matching products from the Algolia index instant_search. This example uses custom rendering with Preact components.

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
...
autocomplete({
  ...
  // add the product display
  getSources({ query, state }) {
    if (!query) {
    return [];
  }
  return [
    {
      sourceId: 'products',        
      getItems() {
        // Get the products from an Algolia index
        return getAlgoliaResults({
          searchClient,
          queries: [
            {
              indexName: 'instant_search',
              query,
              params: {            
                hitsPerPage: 3,                        
                attributesToSnippet: ['name:10'],
                snippetEllipsisText: '',
              },
            },
          ],
        });
      },
      // Control how products are shown in Autocomplete
      templates: {
        // Show `Products` before the search results
        header() {
          return (
            <Fragment>
              <span className="aa-SourceHeaderTitle">Products</span>
              <div className="aa-SourceHeaderLine" />
            </Fragment>
          );
        },
        // Control the rendering of the actual product
        item({ item, components }) {
          return (
            // This Preact component is defined below 
            <ProductItem
              hit={item}
              components={components}
            />
          );
        },
        // Display this text when no products match the query
        noResults() {
          return 'No products for this query.';
        },
      },
    }
  ];
  },
});

// A Preact component to render a product item with an image on the left and
// product details on the right
function ProductItem({ hit, components }) {
    return (
      <a href={hit.url} className="aa-ItemLink">
        <div className="aa-ItemContent">
          <div className="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
            <img src={hit.image} alt={hit.name} width="40" height="40" />
          </div>
          <div className="aa-ItemContentBody">
            <div className="aa-ItemContentTitle">
              <components.Snippet hit={hit} attribute="name" />
            </div>
            <div className="aa-ItemContentDescription">
              From <strong>{hit.brand}</strong> in{' '}
              <strong>{hit.categories[0]}</strong>
            </div>
            {hit.rating > 0 && (
              <div className="aa-ItemContentDescription">
                <div style={{ display: 'flex', gap: 1, color: '#ffc107' }}>
                  {Array.from({ length: 5 }, (_value, index) => {
                    const isFilled = hit.rating >= index + 1;
  
                    return (
                      <svg
                        key={index}
                        width="16"
                        height="16"
                        viewBox="0 0 24 24"
                        fill={isFilled ? 'currentColor' : 'none'}
                        stroke="currentColor"
                        strokeWidth="3"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      >
                        <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
                      </svg>
                    );
                  })}
                </div>
              </div>
            )}
            <div className="aa-ItemContentDescription" style={{ color: '#000' }}>
              <strong>${hit.price.toLocaleString()}</strong>
            </div>
          </div>
        </div>
        <div className="aa-ItemActions">
          <button
            className="aa-ItemActionButton aa-DesktopOnly aa-ActiveOnly"
            type="button"
            title="Select"
            style={{ pointerEvents: 'none' }}
          >
            <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
              <path d="M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z" />
            </svg>
          </button>
          <button
            className="aa-ItemActionButton"
            type="button"
            title="Add to cart"
          >
            <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
              <path d="M19 5h-14l1.5-2h11zM21.794 5.392l-2.994-3.992c-0.196-0.261-0.494-0.399-0.8-0.4h-12c-0.326 0-0.616 0.156-0.8 0.4l-2.994 3.992c-0.043 0.056-0.081 0.117-0.111 0.182-0.065 0.137-0.096 0.283-0.095 0.426v14c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h14c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-14c0-0.219-0.071-0.422-0.189-0.585-0.004-0.005-0.007-0.010-0.011-0.015zM4 7h16v13c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-14c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707zM15 10c0 0.829-0.335 1.577-0.879 2.121s-1.292 0.879-2.121 0.879-1.577-0.335-2.121-0.879-0.879-1.292-0.879-2.121c0-0.552-0.448-1-1-1s-1 0.448-1 1c0 1.38 0.561 2.632 1.464 3.536s2.156 1.464 3.536 1.464 2.632-0.561 3.536-1.464 1.464-2.156 1.464-3.536c0-0.552-0.448-1-1-1s-1 0.448-1 1z" />
            </svg>
          </button>
        </div>
      </a>
    );
}

Add product categories as third data source

To add categories to the federated search, import getAlgoliaFacets from autocomplete-js.

1
2
import { autocomplete, getAlgoliaResults, getAlgoliaFacets } from '@algolia/autocomplete-js';
...

Now add getAlgoliaFacets to getSources to add the product categories:

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
...
autocomplete({
  ...
  getSources() {
    return [
      {
        // The source for the products
      ...
      },
      {
        sourceId: 'productsCategories',
        getItems({ query }) {
          // use the product categories as facets
          return getAlgoliaFacets({
            searchClient,
            queries: [
              {
                indexName: 'instant_search',
                facet: 'hierarchicalCategories.lvl1',
                params: {
                  facetQuery: query, 
                  maxFacetHits: 2,
                },
              },
            ],
          });
        },
        // Control the rendering of the product categories
        templates: {
          header() {
            return (
              // Show 'Product Categories' before the actual categories
              <Fragment>
                <span className="aa-SourceHeaderTitle">Products Categories</span>
                <div className="aa-SourceHeaderLine" />
              </Fragment>
            );
          },
          item({ item }) {
            return (
              <div>{item.label}</div>
            );
          }                
        },
      },
    ];
  },
});

Going further

Beyond this example, you can add more data sources. When adding your data sources, Algolia recommends encapsulating them in plugins. This makes them more modular, reusable, and distributable. See Building your own plugin for more information.

You can also add more features, such as:

Did you find this page helpful?