Google place autocomplete integration with Angular 11 using the reactive form
Google place autocomplete integration with Angular 11 using the reactive form

Google place autocomplete integration with Angular 11 is now easy to build and use.

Getting addresses from customers is so common these days in any application. Verifying the received address is going to be a challenge. We can overcome this by using Google place autocomplete to get the suggested address and even more accurate address.

What is Google Place Autocomplete

According to Google documentation, The Place Autocomplete service is a web service that returns place predictions in response to an HTTP request.

The request specifies a textual search string and optional geographic bounds. The service can be used to provide autocomplete functionality for text-based geographic searches, by returning places such as businesses, addresses, and points of interest as user types.

To know more about the documentation, visit the place autocomplete

Build Address component using Place Autocomplete

In this example, I will create the following to build the final address component that uses the Google Place Autocomplete feature.

  1. A service named "google-address" provides methods to exact the google data.
  2. Address component that will be used to collect address from the user or autoselect suggested address from autocomplete. This is built using Reactive Forms.

Steps to build Place Autocomplete feature.

Step 1:

Before you start using the Places API, you need a project with a billing account and the Places API enabled.

once you have the Key, use the below script in your HTML to load the library from google.


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

Step 2:

Install package "@types/googlemaps" of version ^3.43.3. Your package will look like "@types/googlemaps": "^3.43.3".

In the below code, you can specify what type of addresses you are allowing the user to find in the search result. I have not specified any type of address type so I get to see the address using Entity name, direct address as well.


const autocomplete = new google.maps.places.Autocomplete(
      this.addresstext.nativeElement,
      {
        componentRestrictions: { country: 'US' },
        types: [this.addressType]  // 'establishment' / 'address' / 'geocode' // we are checking all types
      }
    );

Address Component HTML

<nav aria-label="breadcrumb">
    <ol class="breadcrumb">
        <li class="breadcrumb-item"><a href="#">Home</a></li>
        <li class="breadcrumb-item" aria-current="page">Google Address</li>
        <li class="breadcrumb-item active" aria-current="page"> Address type: <b>{{addressType}}</b></li>
    </ol>
</nav>

<div class="container">
    <h1> Google Places Autocomplete </h1>
    <form [formGroup]="addressForm">
        <div class="row">
            <div class="col-md-6">
                <div class="form-group">
                    <label for="addressLine1">Street line 1</label>
                    <input type="text" #addresstext required="required" formControlName="addressLine1"
                        autocomplete="off" class="form-control" id="addressLine1" aria-describedby="emailHelp"
                        placeholder="Enter street line 1">
                    <small id="addressLine1" class="form-text text-muted">Start typing address for auto complete</small>
                </div>
                <div class="form-group">
                    <label for="city">City</label>
                    <input type="text" class="form-control" id="addressLine2" aria-describedby="city"
                        formControlName="city" placeholder="Enter city">
                </div>
                <div class="form-group">
                    <label for="city">State</label>
                    <select class="form-control" formControlName="state" id="state">
                        <option value="">- - Select - -</option>
                        <option value="AL">Alabama</option>
                        <option value="NJ">New Jersey</option>
                        <option value="NY">New York</option>
                    </select>
                </div>
            </div>
            <div class="col-md-6">
                <div class="form-group">
                    <label for="addressLine2">Street line 2</label>
                    <input type="text" class="form-control" id="addressLine2" aria-describedby="addressLine2"
                        formControlName="addressLine2" placeholder="Enter street line 2">
                </div>
                <br>
                <div class="form-group">
                    <label for="city">Postal Code</label>
                    <input type="text" class="form-control" id="postalCode" aria-describedby="postalCode"
                        formControlName="postalCode" placeholder="Enter postal code">
                </div>
                <div class="form-group">
                    <label for="city">Country</label>
                    <select class="form-control" formControlName="country" id="country">
                        <option value="">- - Select - -</option>
                        <option value="US">United States of America</option>
                    </select>
                </div>
            </div>
        </div>
    </form>

    <div *ngIf="showDetails">
        <div class="card">    
         
            <div class="card-body">
                <div class="panel-heading"><h2>Google Response Details</h2></div>   <br>
                <h5>Formatted Address</h5>
                <p>
                    {{formattedAddress}}
                </p>
                <br>
                <h5>Google entire response object</h5>
                <pre>
                    {{place | json}}
                </pre>
            </div>

        </div>

    </div>
</div>

Address Component TS file

In this component, to retrieve the specific address data, I have built a custom service that exacts the information.

import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { GoogleAddress } from '../model/address';
import { GoogleAddressService } from '../service/google-address.service';

@Component({
  selector: 'app-address',
  templateUrl: './address.component.html',
  styleUrls: ['./address.component.scss']
})
export class AddressComponent implements OnInit {
  showDetails = false;
  @Input() addressType: string;
  place!: object;
  @ViewChild('addresstext') addresstext: any;
  
  establishmentAddress: Object;

  formattedAddress: string;
  formattedEstablishmentAddress: string;

  addressForm!: FormGroup;
  googleAddress!: GoogleAddress;

  constructor(private googleAddressService: GoogleAddressService,
    private formBuilder: FormBuilder) { }

  ngOnInit(): void {
    this.initializeAddressForm();
  }

  initializeAddressForm() {
    const initialAddress : GoogleAddress =  {
      addressLine1: '', addressLine2: '', city: '' , state: '', country: '', postalCode: ''
    };
    this.googleAddress = initialAddress;

    this.addressForm = this.formBuilder.group({
      addressLine1: new FormControl(this.googleAddress.addressLine1, [Validators.required,
      Validators.maxLength(200)]),
      addressLine2: new FormControl(this.googleAddress.addressLine2),
      city: new FormControl(this.googleAddress.city, [Validators.required,
      Validators.maxLength(100)]),
      state: new FormControl(this.googleAddress?.state, [Validators.required,
      Validators.maxLength(50)]),
      postalCode: new FormControl(this.googleAddress.postalCode, [Validators.required,
      Validators.maxLength(15)]),
      country: new FormControl(this.googleAddress?.country, [Validators.required,
      Validators.maxLength(50)])
    });
  }

  ngAfterViewInit() {
    this.getPlaceAutocomplete();
  }

  private getPlaceAutocomplete() {
    const autocomplete = new google.maps.places.Autocomplete(
      this.addresstext.nativeElement,
      {
        componentRestrictions: { country: 'US' },
        types: [this.addressType]  // 'establishment' / 'address' / 'geocode' // we are checking all types
      }
    );
    google.maps.event.addListener(autocomplete, 'place_changed', () => {
      this.place = autocomplete.getPlace();
      this.formattedAddress = this.googleAddressService.getFormattedAddress(this.place);
      this.patchGoogleAddress();
      this.showDetails = true;
    });
  }

  patchGoogleAddress() {
    const streetNo = this.googleAddressService.getStreetNumber(this.place);
    const street = this.googleAddressService.getStreet(this.place);
    let googleAddress: GoogleAddress = {
      addressLine1: `${streetNo === undefined ? '' : streetNo} ${street === undefined ? '' : street
        }`,
      addressLine2: '',
      postalCode: this.googleAddressService.getPostCode(this.place),
      city: this.googleAddressService.getLocality(this.place),
      state: this.googleAddressService.getState(this.place),
      country: this.googleAddressService.getCountryShort(this.place),
    };
    this.addressForm.markAllAsTouched();
    this.patchAddress(googleAddress);
  }

  patchAddress(address: GoogleAddress) {
    if (this.addressForm !== undefined) {
      this.addressForm
        .get('addressLine1')!
        .patchValue(address.addressLine1);
      this.addressForm
        .get('addressLine2')!
        .patchValue(address.addressLine2);
      this.addressForm.get('postalCode')!.patchValue(address.postalCode);
      this.addressForm.get('city')!.patchValue(address.city);
      this.addressForm.get('state')!.patchValue(address.state);
      this.addressForm.get('country')!.patchValue(address.country);
    }
  }
}

Please note that I have filled the states and Country with few values. you can fill the dropdown with all possible states and countries.

Step 3:

Building custom google address service for ease of use.

GoogleAddressService

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class GoogleAddressService {

  constructor() { }

  getPlaceId(place: object){
    return place['place_id'];
}

getFormattedAddress(place: object){
  return place['formatted_address'];
}

getAddrComponent(place, componentTemplate) {
  let result;

  for (let i = 0; i < place.address_components.length; i++) {
    const addressType = place.address_components[i].types[0];
    if (componentTemplate[addressType]) {
      result = place.address_components[i][componentTemplate[addressType]];        
      return result;
    }
  }
  return;
}

getStreetNumber(place) {
  const COMPONENT_TEMPLATE = { street_number: 'short_name' },
    streetNumber = this.getAddrComponent(place, COMPONENT_TEMPLATE);

  return streetNumber===undefined?'':streetNumber;
}

getStreet(place) {
  const COMPONENT_TEMPLATE = { route: 'long_name' },
    street = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return street;
}

getLocality(place) {
  const COMPONENT_TEMPLATE = { locality: 'long_name' },
    city = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return city;
}

getState(place) {
  const COMPONENT_TEMPLATE = { administrative_area_level_1: 'short_name' },
    state = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return state;
}

getDistrict(place) {
  const COMPONENT_TEMPLATE = { administrative_area_level_2: 'short_name' },
    state = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return state;
}

getCountryShort(place) {
  const COMPONENT_TEMPLATE = { country: 'short_name' },
    countryShort = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return countryShort;
}

getCountry(place:any) {
  const COMPONENT_TEMPLATE = { country: 'long_name' },
    country = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return country;
}

getPostCode(place) {
  const COMPONENT_TEMPLATE = { postal_code: 'long_name' },
    postCode = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return postCode;
}

getPhone(place) {
  const COMPONENT_TEMPLATE = { formatted_phone_number: 'formatted_phone_number' },
    phone = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return phone;
}
}

The formatted address property is the address that you will see in google suggestions on the dropdown list.

Google Place Autocomplete Demo

Complete Application Code

You may download the entire working code from GitHub repository

Tips and Troubleshooting

After placing all the above code if the auto-complete feature is not working then you need to verify the below settings and fix them in order for it to work.

  1. go to tsconfig.json file and add ""node_modules/@types"  under "typeRoots"
tsconfig file for google place autocomplete

Conclusion

In this post, you learned how to use Google place autocomplete integration with Angular 11 and more tips to make it work with the latest Angular and typescript code.

2 thoughts on “Google place autocomplete integration with Angular 11 using the reactive form

Leave a Reply

Your email address will not be published. Required fields are marked *

Verified by MonsterInsights