How to search google places with Ionic ?

In this tutorial, i will show you how to use google places sdk to search places in a ionic application and display results to our users.

The source code repository

Generate a Google places api key and add it to the project

To use the Google places API, you need to have an API Key and you can get one here

For this tutorial, we will create a new ionic application. Launch a terminal and enter:

ionic start googleplaces blank --capacitor --project-id=googleplaces --package-id=com.ionicandjdangotutorials.googleplaces

With the –project-id argument we can specify our application name and with the –package-id argument the bundle identifier of our application.

If you are not familiar with these values you can learn more here.

Now we can add our Google Places API key in the index.html file of our ionic project:

 <script src="https://maps.googleapis.com/maps/api/js?v=3&key=YOURAPIKEY&libraries=places"></script>

Replace the YOURAPIKEY with the value you obtain from google. This line tells Ionic to load the Google places javascript sdk.

Add Google places search with Ionic

We will modify the home.page.html file to add a search box and add a list of search results.

<ion-header no-border mode="ios">
  <ion-toolbar mode="ios">
    <ion-title>Search google places</ion-title>
  </ion-toolbar>
</ion-header>
<ion-content>
  <div class="page-flex-align" padding>
    <div class="top-content">
      <h1>Destination address</h1>
      <p>Please type the address you would like to find</p>
      <ion-searchbar class="location-search" mode="ios" [(ngModel)]="autocomplete.query" [showCancelButton]="false" (ionInput)="updateSearch()"
      (ionCancel)="dismiss()" (ionClear)="dismiss()" placeholder="">
    </ion-searchbar>
    <p class="small" *ngIf="selectedItem">{{autocomplete.query}}</p>

      
      <ion-list lines="full">
     
        <ion-item  *ngFor="let item of items" (click)="chooseItem(item)">
          <ion-icon slot="end" color="success" size="small" class="wg-arrow-line-left">
          </ion-icon>
          <ion-label text-wrap>
            <h2>{{item.structured_formatting.main_text}}</h2>
            <p>{{item.structured_formatting.secondary_text}}</p>
          </ion-label>
        </ion-item>
      </ion-list>
      <ion-button [disabled]="buttonDisabled" mode="ios" expand="block" shape="round" (click)="validDestination()">
        Validate</ion-button>
    </div>
  </div>
</ion-content>

We store the value entered in the search box in a variable called autocomplete.query

[(ngModel)]="autocomplete.query" [showCancelButton]="false" (ionInput)="updateSearch()"

And each time an input is typed, we call the method updateSearch().

Now let’s move on to the implementation. First we will geolocate the user using Capacitor. To do this edit the home.page.ts and add the method

  geoloc() {
    return new Promise(async resolve => {
      if (this.platform.is('capacitor')) {
        const position = await Geolocation.getCurrentPosition();
        if (position){
          resolve(position);
        }
        else{
        }
        console.log("------  PLATFORM capacitor");
      }
      else {
        // webgeoloc
      
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(
            position => {
              console.log("============= POSITION  ================");
              console.log(position)
              resolve(position);
            },
            error => {
              resolve(false);
            }
          );
        }
      }
    })
  }

We check if the user is on mobile an use capacitor native geoloc plugin to get the coordinates of his position, otherwise we use the browser geolocation. This method is called when our view is entered

 async ionViewWillEnter() {
    this.items=[]
    this.autocomplete.query=""
    const position = await Geolocation.getCurrentPosition();
     if (position) {
      console.log(position)
       this.currentLat = position.coords.latitude
       this.currentLon = position.coords.longitude
     }
  } 

We also initialize an empty items array which will contain the google places results and our autocomplete.query variable that will contains the input of the user.

We also need to initialize the Google places API as soon as our page is initialized

 constructor(
    public platform: Platform) {
    this.initPage()
  }

  initPage() {
    // Create a new session token.
    this.sessionToken = new google.maps.places.AutocompleteSessionToken();
    this.acService = new google.maps.places.AutocompleteService();
    this.items = [];
    this.autocomplete = {
      query: ''
    };
  }

The initPage() method creates a new session token and initialise the AutocompleteService using the google places sdk

Now the most important method is to get places each time our user enters a value

  updateSearch() {
    console.log('modal > updateSearch '+this.autocomplete.query);
    if (this.autocomplete.query == '') {
      this.items = [];
      this.buttonDisabled = true
      return;
    }
    let self = this;
    let config: any;
    if (this.currentLat) {
      let myLatLng = new google.maps.LatLng({lat: this.currentLat, lng: this.currentLon}); 
      config = {
        types: ['geocode'], // other types available in the API: 'establishment', 'regions', and 'cities'
        input: this.autocomplete.query,
        sessionToken: this.sessionToken,
        language: "EN",
        location: myLatLng,
        radius: 500 * 100 //50Km
        //, 
        //componentRestrictions: { country: 'FR,ES,BE' } 
      }

    }
    else {
      config = {
        types: ['geocode'], // other types available in the API: 'establishment', 'regions', and 'cities'
        input: this.autocomplete.query,
        sessionToken: this.sessionToken,
        language:"EN"
        //location: {lat: -34, lng: 151},
        //radius: 1000 * 100 //100Km
        //, 
        //componentRestrictions: { country: 'FR,ES,BE' } 
      }

    }

    console.log(config)
    this.acService.getPlacePredictions(config, function (predictions, status) {
      //console.log('modal > getPlacePredictions > status > ', status);
      self.items = [];
      //console.log("predictions "+JSON .stringify(predictions)) 
      if (predictions) {
        predictions.forEach(function (prediction) {
          self.items.push(prediction);
        });
      }
    });

  }

To get places we use the getPlacePredictions method which will return a list of predictions (the places themselves)

 this.acService.getPlacePredictions(config, function (predictions, status) {
      //console.log('modal > getPlacePredictions > status > ', status);
      self.items = [];
      //console.log("predictions "+JSON .stringify(predictions)) 
      if (predictions) {
        predictions.forEach(function (prediction) {
          self.items.push(prediction);
        });
      }
    });

We need to pass to this method a config object

 config = {
        types: ['geocode'], // other types available in the API: 'establishment', 'regions', and 'cities'
        input: this.autocomplete.query,
        sessionToken: this.sessionToken,
        language: "EN",
        location: myLatLng,
        radius: 500 * 100 //50Km
        //, 
        //componentRestrictions: { country: 'FR,ES,BE' } 
      }

Many parameters can be set such as the type of results that we want, the langage in which results should be, the location of the user, the radius of the search,…

Once our results are displayed on our screen if the user selects a particular value, we can assign it

 validDestination() {
    if (this.selectedItem == undefined) {
      // should display a message to the user
      console.log("Enter a destination")
    }
    else {
      let latitude = this.selectedItem.latitude;
      let longitude = this.selectedItem.longitude;
      console.log("Ok selected item "+JSON.stringify(this.selectedItem))
    }
  }

  chooseItem(item: any) {
    console.log('modal > chooseItem > item > ', item);
    console.log(item)
    this.selectedItem = item;
    this.items = [];
    this.autocomplete.query = item.structured_formatting.main_text + " - " + item.structured_formatting.secondary_text;
    this.buttonDisabled = false;
    if (item.structured_formatting.secondary_text.indexOf(",")>0){
      let lieuSplitted = item.structured_formatting.secondary_text.split(",",1); 
      this.destinationCity  = lieuSplitted[0]
    }
    else{
      this.destinationCity  = item.structured_formatting.main_text
    }
  }

And voila. You can do whatever you want, once the user selects a google places value.

Ionic Google places

Here is the full home.page.ts code with import and variable declarations and get the full project code here : source code repository

import { Component, OnInit } from '@angular/core';
import { Platform } from '@ionic/angular';
const { Geolocation } = Plugins;
import { Plugins } from '@capacitor/core';
declare var google;

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
   
  items: any;
  autocomplete: any;
  acService: any;
  placesService: any;
  selectedItem: any;
  buttonDisabled = true;
  sessionToken: any;
  currentLon: any;
  currentLat: any;
  destinationCity : string;
  zipCode : string="";
  constructor(
    public platform: Platform) {
    this.initPage()
  }



  geoloc() {
    return new Promise(async resolve => {
      if (this.platform.is('capacitor')) {
         
        const position = await Geolocation.getCurrentPosition();
        if (position){
          resolve(position);
        }
        else{
           
        }
        
        console.log("------  PLATFORM capacitor");
       
      }
      else {
        // webgeoloc
      
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(
            position => {
              console.log("============= POSITION  ================");
              console.log(position)
              //Hardcoded 

              resolve(position);
            },
            error => {

              resolve(false);
            }
          );
        }
      }
    })
  }

  goBack() {
   
  }

  dismiss() {
    console.log("Clear search")
    this.items = [];
    this.autocomplete = {
      query: ''
    };
   
  }
 
  initPage() {
    // Create a new session token.
    this.sessionToken = new google.maps.places.AutocompleteSessionToken();
    this.acService = new google.maps.places.AutocompleteService();
    this.items = [];
    this.autocomplete = {
      query: ''
    };
  }

  async ionViewWillEnter() {
    this.items=[]
    this.autocomplete.query=""
    
    const position = await Geolocation.getCurrentPosition();
     
     if (position) {
      console.log(position)
       this.currentLat = position.coords.latitude
       this.currentLon = position.coords.longitude
     }
    
  } 

  ngOnInit() {

  } 
   
  validDestination() {
    if (this.selectedItem == undefined) {
      // should display a message to the user
      console.log("Enter a destination")
    }
    else {
      let latitude = this.selectedItem.latitude;
      let longitude = this.selectedItem.longitude;
      console.log("Ok selected item "+JSON.stringify(this.selectedItem))
    }
  }

  chooseItem(item: any) {
    console.log('modal > chooseItem > item > ', item);
    console.log(item)
    this.selectedItem = item;
    this.items = [];
    this.autocomplete.query = item.structured_formatting.main_text + " - " + item.structured_formatting.secondary_text;
    this.buttonDisabled = false;
    if (item.structured_formatting.secondary_text.indexOf(",")>0){
      let lieuSplitted = item.structured_formatting.secondary_text.split(",",1); 
      this.destinationCity  = lieuSplitted[0]
    }
    else{
      this.destinationCity  = item.structured_formatting.main_text
    }
  }

  updateSearch() {
    console.log('modal > updateSearch '+this.autocomplete.query);
    if (this.autocomplete.query == '') {
      this.items = [];
      this.buttonDisabled = true
      return;
    }
    let self = this;
    let config: any;
    if (this.currentLat) {
      let myLatLng = new google.maps.LatLng({lat: this.currentLat, lng: this.currentLon}); 
      config = {
        types: ['geocode'], // other types available in the API: 'establishment', 'regions', and 'cities'
        input: this.autocomplete.query,
        sessionToken: this.sessionToken,
        language: "EN",
        location: myLatLng,
        radius: 500 * 100 //50Km
        //, 
        //componentRestrictions: { country: 'FR,ES,BE' } 
      }

    }
    else {
      config = {
        types: ['geocode'], // other types available in the API: 'establishment', 'regions', and 'cities'
        input: this.autocomplete.query,
        sessionToken: this.sessionToken,
        language:"EN"
        //location: {lat: -34, lng: 151},
        //radius: 1000 * 100 //100Km
        //, 
        //componentRestrictions: { country: 'FR,ES,BE' } 
      }

    }

    console.log(config)
    this.acService.getPlacePredictions(config, function (predictions, status) {
      //console.log('modal > getPlacePredictions > status > ', status);
      self.items = [];
      //console.log("predictions "+JSON .stringify(predictions)) 
      if (predictions) {
        predictions.forEach(function (prediction) {
          self.items.push(prediction);
        });
      }
    });

  }
}

Christophe Surbier