Guides / Building Search UI

Migration from v3 to v4

Upgrade Angular to 10.x.x or later

Angular InstantSearch follows the support policy of Angular. Support for Angular < 10 is deprecated.

Use InstantSearch.js types

Angular InstantSearch now fully relies on InstantSearch.js types. If you rely on the types exposed by Angular InstantSearch, you need to replace them with their InstantSearch.js equivalent.

1
2
- import type { CurrentRefinementsState } from 'angular-instantsearch';
+ import type { CurrentRefinementsRenderState } from 'instantsearch.js/es/current-refinements/connectCurrentRefinements';

InstantSearch.js is now a dependency

InstantSearch.js is no longer referenced as a peer dependency. Make sure to uninstall your version and use the one installed with Angular InstantSearch.

Favor using TypedBaseWidget over BaseWidget

A type-capable alternative to BaseWidget called TypedBaseWidget was introduced. This is now the preferred way to create a custom widgets using the InstantSearch.js Connector API.

This is an optional change.

1
2
3
4
5
6
7
+ import type {
+   CurrentRefinementsWidgetDescription,
+   CurrentRefinementsConnectorParams,
+ } from 'instantsearch.js/es/current-refinements/connectCurrentRefinements';

- export class CustomCurrentRefinements extends BaseWidget { /* ... */ };
+ export class CustomCurrentRefinements extends TypedBaseWidget<CurrentRefinementsWidgetDescription, CurrentRefinementsConnectorParams> { /* ... */ };

Custom widgets require parentIndex and instantSearchInstance

Since we now support a new “parent” widget called ais-index, you need to change the constructor of your custom widget if it extends the BaseWidget class:

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
import { Component, Inject, forwardRef, OnInit, Input } from '@angular/core'
import { BaseWidget, NgAisInstantSearch } from 'angular-instantsearch'
import { connectRefinementList } from 'instantsearch.js/es/connectors'

@Component({
  selector: 'my-widget',
  template: '',
})
export class MyWidget extends BaseWidget implements OnInit {
  @Input() public attribute: string

  constructor(
+    @Inject(forwardRef(() => NgAisIndex))
+    @Optional()
+    public parentIndex: NgAisIndex,
    @Inject(forwardRef(() => NgAisInstantSearch))
-    public instantSearchParent: NgAisInstantSearch
+    public instantSearchInstance: NgAisInstantSearch
  ) {
    super('MyWidget')
  }

  public ngOnInit() {
    this.createWidget(connectRefinementList, {
      attribute: this.attribute,
    })
    super.ngOnInit()
  }
}

Custom widgets using the helper API

This release includes version 3 of the algoliasearch-helper package. If you only use the built-in widgets or connectors, nothing changes for you.

This version of algoliasearch-helper no longer includes Lodash, which significantly reduces its bundle size (from 27.5KB to 9.1KB Gzipped). If you’re using any methods from the helper, searchParameters or searchResults in custom widgets, please refer to the detailed change log of the package.

Custom widgets using a custom connector

Because the algoliasearch-helper package uses version 3, you have to replace the getConfiguration lifecycle with getWidgetSearchParameters and getWidgetState.

This also means that your custom widget takes part in the routing. You can exclude it from the URL via stateMapping.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const connector = () => ({
  getConfiguration(searchParams) {
    return {
      disjunctiveFacets: ['myAttribute'],
    }
  },
  getWidgetSearchParameters(searchParameters, { uiState }) {
    return searchParameters.addDisjunctiveFacetRefinement(
      'myAttribute',
      uiState.myWidgetName.myAttribute
    )
  },
  getWidgetState(uiState, { searchParameters }) {
    return {
      ...uiState,
      myWidgetName: {
        myAttribute: searchParameters.getDisjunctiveRefinements('myAttribute'),
      },
    }
  },
})

Becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const connector = () => ({
  getWidgetSearchParameters(searchParameters, { uiState }) {
    return searchParameters
      .addDisjunctiveFacet('myAttribute')
      .addDisjunctiveFacetRefinement(
        'myAttribute',
        uiState.myWidgetName.myAttribute
      )
  },
  getWidgetState(uiState, { searchParameters }) {
    return {
      ...uiState,
      myWidgetName: {
        myAttribute: searchParameters.getDisjunctiveRefinements('myAttribute'),
      },
    }
  },
})

searchParameters option for ais-instantsearch

We removed the searchParameters option from the ais-instantsearch widget. You can easily replace it with the ais-configure widget, like this:

1
2
3
4
5
6
7
<ais-instantsearch
-  [config]="{ /* other config */ searchParameters: { hitsPerPage: 5 } }"
+  [config]="{ /* other config */ }"
>
  <!-- children -->
+  <ais-configure [searchParameters]="{ hitsPerPage: 5 }"></ais-configure>
</ais-instantsearch>

You can now add initialUiState to your instantsearch widget. This overwrites specific search parameters that would otherwise be set during widget instantiation.

initialUiState is only taken into account if a widget owning that state is mounted. A warning is displayed in development mode explaining which widget needs to be added for the UI state to have an effect.

A good example of this is the ais-refinement-list widget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<ais-instantsearch
  [config]="{
    /* other config */
-    searchParameters: {
-      disjunctiveFacets: ['brand'],
-      disjunctiveFacetsRefinements: {
-        brand: ['Apple'],
-      },
-    },
+    initialUiState: {
+      refinementList: {
+        brand: ['Apple'],
+      },
+    },
  }"
>
  <!-- children -->
  <ais-refinement-list attribute="brands"></ais-configure>
</ais-instantsearch>

If you have a widget that you don’t want to display values for but still want to refine using this method, you can use a “Virtual Widget” like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component, Inject, forwardRef, OnInit, Input } from '@angular/core'
import { BaseWidget, NgAisInstantSearch } from 'angular-instantsearch'
import { connectRefinementList } from 'instantsearch.js/es/connectors'

@Component({
  selector: 'virtual-refinement-list',
  template: '',
})
export class VirtualRefinementList extends BaseWidget implements OnInit {
  @Input() public attribute: string

  public ngOnInit() {
    this.createWidget(connectRefinementList, {
      attribute: this.attribute,
    })
    super.ngOnInit()
  }
}

Routing

Even if you aren’t using multi-index search, the way in which UI state is stored has changed. It used to look like this:

1
2
3
4
{
  "query": "value",
  "page": 5
}

It now looks like this:

1
2
3
4
5
6
{
  "indexName": {
    "query": "value",
    "page": 5
  }
}

If you are using the default state mapping (simpleStateMapping) with the current version, you can replace it with singleIndexStateMapping('yourIndexName'). You have to change the code as followed:

1
2
3
4
5
6
7
8
 searchConfig = {
   indexName: 'myIndex',
   routing: {
-    stateMapping: simple(),
+    stateMapping: singleIndex('myIndex'),
   }
   // ...
 }

If you are using a custom state mapping, you have to loop over the outer level of the index widget and add this extra level to the routeToState. You can check the source for a reference on how to implement this.

For example, a stateMapping that maps a few properties would change like 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
28
29
30
31
32
33
34
35
36
37
38
39
40
// Before
const stateMapping = {
  stateToRoute(uiState) {
    return {
      query: uiState.query,
      page: uiState.page,
      // ...
    }
  },

  routeToState(routeState) {
    return {
      query: routeState.query,
      page: routeState.page,
      // ...
    }
  },
}

// After
const stateMapping = {
  stateToRoute(uiState) {
    const indexUiState = uiState[indexName]
    return {
      query: indexUiState.query,
      page: indexUiState.page,
      // ...
    }
  },

  routeToState(routeState) {
    return {
      [indexName]: {
        query: routeState.query,
        page: routeState.page,
        // ...
      },
    }
  },
}

Configure

The configure widget is now included in the UI state. If you want to exclude it from the URL you can use the default stateMappings or exclude it in your custom state mapping. A good reason to exclude the configure widget from the UI state is to prevent users from adding any search parameters.

You must exclude this widget in both the stateToRoute, to keep it from appearing in the URL and routeToState, so that the URL doesn’t apply to the state.

Check the stateMapping source code for implementation details.

Other breaking changes

If you were using any InstantSearch.js APIs directly, you have to consult the InstantSearch.js v3 to v4 migration guide for any breaking changes.

Migration from v2 to v3

InstantSearch.js is now a peer dependency

with npm:

$
npm install --save instantsearch@^3

with yarn:

$
yarn add instantsearch@^3

RefinementList: “Show more” button controlled by showMore property

The display of the “Show more” button is no longer inferred by the showMoreLimit and the internal state property canToggleShowMore. It’s now controlled by a new input property: showMore: boolean = false.

If you want to use the “Show more” button on the ais-refinement-list, make sure to set the showMore property to true

1
2
3
4
5
<ais-refinement-list
  ...
  [showMore]="true"
>
</ais-refinement-list>

NumericSelector widget has been removed

Use instead ais-numeric-menu.

InfiniteHits: “Show More” button CSS class was renamed

The “Show more” button CSS class has been renamed from showMore to ` loadMore`.

InstantSearch: appId, apiKey and createAlgoliaClient have been removed

This enforces the usage of the searchClient option. createAlgoliaClient has been removed as there is no longer a use case for it.

If you have (something like) the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component({
  template: `
    <ais-instantsearch [config]="config">
      <!-- more widgets -->
    </ais-instantsearch>
  `
})
export class AppComponent {
  config = {
    appId: 'YourApplicationID',
    apiKey: 'YourSearchOnlyAPIKey',
    /* ... */
  };
}

Replace it with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import algoliasearch from 'algoliasearch/lite';

@Component({
  template: `
    <ais-instantsearch [config]="config">
      <!-- more widgets -->
    </ais-instantsearch>
  `
})
export class AppComponent {
  config = {
    searchClient: algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey'),
    /* ... */
  };
}

SortBy items property changed

The name key in items has been renamed to value.

Replace this:

1
2
3
4
5
6
7
<ais-sort-by
  [items]="[
    { name: 'products', label: 'Most relevant' },
    { name: 'products_price_desc', label: 'Highest price' }
  ]"
>
</ais-sort-by>

With this:

1
2
3
4
5
6
7
<ais-sort-by
  [items]="[
    { value: 'instant_search', label: 'Featured' },
    { value: 'instant_search_price_asc', label: 'Price asc.' },
    { value: 'instant_search_price_desc', label: 'Price desc.' }
  ]"
></ais-sort-by>

Migration from v0 to v1

Angular 2 and 4 support drop

To support Angular CLI 6 support for older versions was dropped, the only supported versions are now: 5, 6 and 7. You can update your Angular 4 application by following this guide: https://update.angular.io/.

If you are using Angular +6 you will need an extra step, polyfill process.env by adding in your src/polyfill.ts:

1
(window as any).process = {env: {}};

Widget prefix

The ng- prefix is considered reserved for core implementations into Angular so it was dropped. All the widgets are now only starting with ais-:

1
2
3
4
5
6
7
8
9
10
11
<ais-instantsearch [config]="{...}">
  <ais-hits>
    <ng-template let-hits="hits">
      <div *ngFor="let hit of hits">
        Hit {{hit.objectID}}:
        <ais-highlight attribute="name" [hit]="hit">
        </ais-highlight>
      </div>
    </ng-template>
  </ais-hits>
</ais-instantsearch>

Server-side rendering

  • The createSSRAlgoliaClient until has been renamed to createSSRSearchClient
  • You can’t use the new routing: true option on <ais-instantsearch> widget until resolution of preboot#82

Did you find this page helpful?