Concepts / Building Search UI / Geo search
May. 10, 2019

You are reading the documentation for Angular InstantSearch v3, which is in beta. You can find the v2 documentation here.

Introduction

We will see how we can leverage the geo search capabilities of Algolia with the geoSearch connector. This demo is built with Google Maps but the core logic is not tied to any map provider. You can build your own map widget with a different provider (e.g. Leaflet).

Since wrapping Google Maps for Angular isn’t trivial, in this guide we will use the Angular Google Maps (AGM) library to save time.

Displaying hits

First of all, let’s add a GeoSearchComponent extending BaseWidget and using geoSearch.

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

@Component({
  selector: "app-geo-search",
  template: ` `
})
export class GeoSearchComponent extends BaseWidget {
  state: {
    clearMapRefinement: Function;
    hasMapMoveSinceLastRefine: Function;
    isRefineOnMapMove: Function;
    isRefinedWithMap: Function;
    items: { name: string; _geoloc: { lat: number; lng: number } }[];
    refine: Function;
    setMapMoveSinceLastRefine: Function;
    toggleRefineOnMapMove: Function;
    position: object
  };

  constructor(
    @Inject(forwardRef(() => NgAisInstantSearch))
    public instantSearchParent
  ) {
    super("GeoSearchComponent");
  }

  public ngOnInit() {
    this.createWidget(connectGeoSearch, {});
    super.ngOnInit();
  }
}

Now let’s introduce Angular Google Maps (AGM) inside our app, along with our newly created GeoSearchComponent.

$
$
npm install --save @agm/core
yarn add @agm/core
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { NgAisModule } from "angular-instantsearch";

import { AppComponent } from "./app.component";

import { AgmCoreModule } from "@agm/core";
import { GeoSearchComponent } from "./geo-search.component";

@NgModule({
  declarations: [AppComponent, GeoSearchComponent],
  imports: [
    NgAisModule.forRoot(),
    BrowserModule,
    AgmCoreModule.forRoot({
      apiKey: "AIxxxxx"
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Now that we have access to the data from the Geo Search connector, we want to add a regular map in the template. Note that to make the map actually visible, we will need to make sure it has a height given. This can be done with a style by selecting the map element and giving it any height.

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
import { Component, forwardRef, Inject } from "@angular/core";
import { BaseWidget, NgAisInstantSearch } from "angular-instantsearch";
import { connectGeoSearch } from "instantsearch.js/es/connectors";

@Component({
  selector: "app-geo-search",
  template: `
    <agm-map
      [latitude]="center.lat"
      [longitude]="center.lng"
      style="height: 600px"
    >
    </agm-map>
  `
})
export class GeoSearchComponent extends BaseWidget {
  state: {
    items: { name: string; _geoloc: { lat: number; lng: number } }[];
  };

  constructor(
    @Inject(forwardRef(() => NgAisInstantSearch))
    public instantSearchParent
  ) {
    super("GeoSearchComponent");
  }
  
  get center() {
    if (this.state.items && this.state.items.length > 0) {
      const [first] = this.state.items;
      return first._geoloc || { lat: 0, lng: 0 };
    }
    return { lat: 0, lng: 0 };
  }

  public ngOnInit() {
    this.createWidget(connectGeoSearch, {});
    super.ngOnInit();
  }
}

The final step is looping over this.state.items to display the hits from this search. Let’s modify our template to do this:

1
2
3
4
5
6
7
8
9
10
11
12
<agm-map
  [latitude]="center.lat"
  [longitude]="center.lng"
  style="height: 600px"
>
  <agm-marker
    [latitude]="item._geoloc.lat"
    [longitude]="item._geoloc.lng"
    [longitude]="item.name"
    *ngFor="let item of state.items || []"
  ></agm-marker>
</agm-map>

That’s it! Now you should have a complete infinite scroll experience! Don’t forget that the complete source code of the example is available on GitHub.

Going further

This guide only explains how to display hits on a map, but geoSearch connector has more features, like refining the search when the map moves, automatically centering on the correct items etc. Feel free to explore the options given to state from the connector to make these experiences.

Did you find this page helpful?