Upgrading Angular InstantSearch
Migration from v3 to v4# A
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 Algolia now supports 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.5 KB to 9.1 KB 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
#
The searchParameters
option has been removed from the ais-instantsearch
widget. However, you can 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 stateMapping
s 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# A
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: 'AJ0P3S7DWQ',
apiKey: '90dfaaf5755e694f341fe68f6e41a6d4',
/* ... */
};
}
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('AJ0P3S7DWQ', '90dfaaf5755e694f341fe68f6e41a6d4'),
/* ... */
};
}
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# A
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 tocreateSSRSearchClient
- You can’t use the new
routing: true
option on<ais-instantsearch>
widget until resolution of preboot#82