Skip to main content
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.
JavaScript
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:
Javascript
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.
JavaScript
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:
HTML
<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.
JavaScript
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.
JavaScript
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