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.

Explore source code

Browse the source for the InstantSearch.js in Angular quickstart example on GitHub.

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 March 23, 2026