Search Suggestions With Algolia
Overview
This is the primary use case of Algolia in our product.
Algolia serves as the Search Engine in our product; i.e. it provides us with real time suggestions to our search query.
The primary component used to provide users for suggestions as they type in our product is
<TsAutoCompleteAlgolia></TsAutoCompleteAlgolia>
This component is placed in the TsHeader.vue organism.
There are certain props that required by this component; and you also need to react to certain emits for this component to function correctly.
This is the complete, working state of the component as of date 11 November 2024
<TsAutoCompleteAlgolia
data-testid="autocomplete-algolia"
@clear="() => algoliaStore.handleClearSearchInput()"
@input="(keyword : string) => algoliaStore.getAlgoliaSuggestions(keyword)"
@enter="(keyword: string, dropdownVisible: boolean) =>
algoliaStore.redirectOnEnterClick(keyword)"
@delete="(option: options) => algoliaStore.deleteSearchHistoryItem(option)"
:last-searched-keyword="algoliaStore.lastTypedKeyword"
:placeholder="useTranslation('searchPlaceholder', 'Waar bent u naar op zoek?')"
v-model="algoliaStore.searchItem"
:suggestions="algoliaStore.suggestions()"
>
</TsAutoCompleteAlgolia>
Props
| Prop | Function |
|---|---|
| last-searched-keyword | We maintain the state of our last searched keyword to show/persist in the search bar, hence we provide this prop with the state from algolia.store.ts |
| placeholder | This is the placeholder text of the search bar. We pass a dynamic translated string to prop |
| suggestions | This props expects an array of objects which the component internally traverses to show the search suggestions |
Emits
| Emit | Function |
|---|---|
| @clear | This emit is fired when the user click on the cross icon on the search bar. This emit clears the search query from the search bar. We have attached a store method algoliaStore.handleClearSearchInput() to it which clears the search input. |
| @input | This emit is fired whenever the user makes an input in the search bar. This emit is triggered whenever there is a change in the internal v-model of the search input. We have attached a store method to this emit algoliaStore.getAlgoliaSuggestions(keyword). This method fetches the suggestions based on current input query. This emit returns a string argument which is the v-model value of the input field inside the component and contains the search query of type string that user entered |
| @enter | This emit is fired when the search bar is focused and the user presses the enter key on the keyboard.This is only triggered if the length of the input query is > 1. We have attached a store method algoliaStore.redirectOnEnterClick(keyword) to it. This emit returns a string argument which is the v-model value of the input field inside the component and contains the search query of type string that user entered. |
| @delete | This emit is fired when the user clicks on the trash-can icon in the search suggestions for a search history item. This emit returns an options object which is the individual search item for which the user clicked the trash-can (delete) icon for. We attach a store method algoliaStore.delteSearchHistoryItem(option) to it. |
Understanding Emits
These methods are described in detail, along with their usage and business logic in the Algolia Store Methos section of the documentation.
Methods used in this section:
- algoliaStore.handleClearSearchInput()
- algoliaStore.getAlgoliaSuggestions(keyword)
- algoliaStore.redirectOnEnterClick(keyword)
- algoliaStore.deleteSearchHistoryItem(option)
Understanding Props
Understanding props for the auto-complete suggestions functionality of algolia is important because it lays the foundational infrastructure for rest of the use cases that algolia has. The props are described at a basic level in the table above but there is one prop that needs a deeper discussion which is suggestions.
suggestions Prop
This props takes in an array of generic type <T> , and the component internally iterates over this array and renders the contents of the array as the options. How the iteration happens, and core functionality of the component is described separately here.
You'll notice that we provide a dynamic value to the suggestions prop, like so:
:suggestions="algoliaStore.suggestions()"
We are providing a store state to this prop.
algoliaStore.suggestions()is a function/method which takes no arguments.algoliaStore.suggestions()returns aref<ComputedSuggestions[]>
suggestions() {
const suggestions = ref<ComputedSuggestions[]>([
{
name: 'Recently Searched',
canDelete: true,
options: computed(() => {
useStateModifier(this.ECOMState, 'success', 'Results Found');
const filteredArray = this.searchHistory.filter(suggestion =>
suggestion.name && suggestion.name.toLowerCase().includes(this.lastTypedKeyword.toLowerCase())
);
if (filteredArray.length === 0) {
return this.searchHistory.slice(0, 5); // Return first 5 elements if no match found
} else {
return filteredArray.slice(0, 5); // Return filtered array if match found
}
}),
stateHandler: 'ECOMState'
},
{
name: 'Popular Keywords',
canDelete: false,
options: computed(() => {
return this.algoliaResponse && this.algoliaResponse.length > 1 ? this.algoliaResponse[0] : []
}),
stateHandler: 'algoliaState'
},
{
name: 'Categories',
canDelete: false,
options: computed(() => {
return this.algoliaResponse && this.algoliaResponse.length > 2 ? this.algoliaResponse[1] : []
}),
stateHandler: 'algoliaState'
},
{
name: 'Products',
canDelete: false,
options: computed(() => {
return this.algoliaResponse && this.algoliaResponse.length === 3 ? this.algoliaResponse[2] : []
}),
stateHandler: 'algoliaState'
}
]);
return suggestions.value;
},
The method suggestions returns a computed ref of the type ComputedSuggestions. It is essentially an array of computed objects. Each individual object can be computed in nature or a normal object as long as it is following the structure specified by the ComputedSuggestions. This is what this type looks like:
export type ComputedSuggestions = {
name: Sections;
canDelete: boolean;
options: options[] | ComputedRef<options[]>;
stateHandler: StateName;
};
ComputedSuggestions has sub-types for each field as well. This is done to ensure strong type safety for the components to as to avert normal type related issues and bugs.
To understand why we had to need to have an array of objects where each individual object is computed, and has multiple options we need to understand the use case for it.
In our Auto-Complete component, we have multiple sections that:
- May or may not consume data from different sources; meaning that each section could consume data from different sources. One might consume data from Algolia, and one might consume data from ECOM.
- Has to be scalable, meaning that we should be able to add or replace sections easily in the future. Hence the architecture should be scalable and flat.
- Each section should have it's own state; so that we can handle each section individually.
Let's understand each key in the ComputedSuggestions type now:
| Key | Type | Description |
|---|---|---|
| name | Sections | Name of the section that we wanna show as the heading. It is supposed to be type safe as well, hence if we want to add a section we also have to add it to the Sections type |
| canDelete | boolean | If we want to show the delete icon for option in a particular section, we will set it to true. Note we have to handle the delete condition separately for that section. |
| options | options | ComputedRef<options> |
| stateHandler | StateName | We pass the name of the state that is associated with this section so that we can manage it with useStateModifier(), which is governed by the Internal State Handling architecture. |
Amidst all the above keys - the most important one remains to be the options key; since this key contains the suggestion items.
It also accepts a ComputedRef<options[]> if we want to pass computed, dynamic options which will be the case majority of the times since we want to show results based on what the user has searched or typed.
You can make API calls, sort, filter static data and whatever you can achieve inside the computed block. You just have to ensure that you return an array of type options; it can either be normal options[] or ComputedRef<options[]>.