Autocomplete with Federated Search
On this page
A federated search offers search results, Query Suggestions, product categories, and other content from multiple indices in a single 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. |
Implementing federated search
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',
params: {
query,
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:
- Merchandising. Use Rules and merchandising to promote queries or items in the empty-search state or no-results state.
- Personalization. - Use Algolia’s personalization feature to enhance Query Suggestions or products.
- Dynamic Re-ranking. Let Algolia’s Dynamic Re-ranking feature boost popular Query Suggestions or products.