Use InstantSearch.js in your Angular projects to manage state and build custom search interfaces.
This approach lets you build a custom search solution without relying on Angular InstantSearch,
which isn’t compatible with the latest Angular versions.
This simplifies development and gives you full control of your search interface.
Base use-case
Rather than adding InstantSearch to a component directly,
store the instance in a service and expose utility methods.
You can reuse a single instance across multiple components and start it from just one component.
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import InstantSearch from 'instantsearch.js/es/lib/InstantSearch';
import type { IndexWidget, Widget } from 'instantsearch.js';
const searchClient = algoliasearch(
'ALGOLIA_APPLICATION_ID',
'ALGOLIA_SEARCH_API_KEY'
);
@Injectable({
providedIn: 'root',
})
export class InstantSearchService {
public instantSearchInstance: InstantSearch;
constructor(router: Router) {
this.instantSearchInstance = new InstantSearch({
searchClient,
indexName: 'instant_search',
future: { preserveSharedStateOnUnmount: true },
});
}
start() {
this.instantSearchInstance.start();
}
addWidgets(widgets: Array<IndexWidget | Widget>) {
this.instantSearchInstance.addWidgets(widgets);
}
removeWidgets(widgets: Array<IndexWidget | Widget>) {
this.instantSearchInstance.removeWidgets(widgets);
}
}
Inject this service into any component to access the InstantSearch instance:
import { Component } from "@angular/core";
import { InstantSearchService } from "../instant-search.service";
@Component({
selector: "app-search",
standalone: true,
templateUrl: "./search.component.html",
})
export class SearchComponent {
ngAfterContentInit() {
this.InstantSearchService.start();
}
}
Add widgets in any component using the service,
whether in the constructor or ngOnInit.
Start the instance in a single component with ngAfterContentInit.
Use connectors to track state changes and bind data to your components.
import { Component } from '@angular/core';
import { InstantSearchService } from '../instant-search.service';
import { BaseHit } from 'instantsearch.js';
import { connectHits, connectSearchBox } from 'instantsearch.js/es/connectors';
@Component({
selector: 'app-search',
standalone: true,
templateUrl: './search.component.html',
})
export class SearchComponent {
public hits: BaseHit[] = [];
public refine: (query: string) => void;
public query: string;
constructor(private InstantSearchService: InstantSearchService) {
this.InstantSearchService.addWidgets([
connectSearchBox(({ refine, query }) => {
this.refine = refine;
this.query = query;
})({
// ...widgetParameters
}),
connectHits(({ hits }) => {
this.hits = hits;
})({}),
]);
}
ngAfterContentInit() {
this.InstantSearchService.start();
}
public search(event: Event) {
this.refine!((event.target as HTMLInputElement).value);
}
}
This lets you have multiple widgets in a single component.
The template automatically re-renders when the state updates with new results:
<input (input)="search($event)" [value]="query" />
<ul>
@for (let hit of hits; track hit.objectID) {
<li>{{ hit.name }}</li>
}
</ul>
Server-side rendering and routing
Because this service reads the Angular Router’s initial state, routing integration works out of the box.
Server-side rendering also works without extra code, as Angular renders the page with the initial state coming from the network request.
import { Injectable } from '@angular/core';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import history from 'instantsearch.js/es/lib/routers/history';
import InstantSearch from 'instantsearch.js/es/lib/InstantSearch';
import type { Widget } from 'instantsearch.js';
import { Router } from '@angular/router';
const searchClient = algoliasearch(
'ALGOLIA_APPLICATION_ID',
'ALGOLIA_SEARCH_API_KEY'
);
@Injectable({
providedIn: 'root',
})
export class InstantSearchService {
public instantSearchInstance: InstantSearch;
constructor(router: Router) {
this.instantSearchInstance = new InstantSearch({
searchClient,
indexName: 'instant_search',
future: { preserveSharedStateOnUnmount: true },
routing: {
router: history({
getLocation: () => {
if (typeof window === 'undefined') {
// No other way to get this in constructor
return new URL(
router['location']._locationStrategy._platformLocation.href
) as unknown as Location;
}
return window.location;
},
}),
},
});
}
}
Multi-index
The index widget supports multiple indices.
import { Component } from '@angular/core';
import { InstantSearchService } from '../instant-search.service';
import { BaseHit } from 'instantsearch.js';
import { connectHits } from 'instantsearch.js/es/connectors';
import index from 'instantsearch.js/es/widgets/index/index';
@Component({
selector: 'app-search',
standalone: true,
templateUrl: './search.component.html',
})
export class SearchComponent {
public hits: BaseHit[] = [];
constructor(private InstantSearchService: InstantSearchService) {
this.InstantSearchService.addWidgets([
index({ indexName: 'instant_search' }).addWidgets([
connectHits(({ hits }) => {
this.hits = hits;
})({}),
]),
]);
}
ngAfterContentInit() {
this.InstantSearchService.start();
}
}
Last modified on January 28, 2026