In this brief tutorial I’ll show you to to quickly build an Angular 2 + Google Maps Places Autocomplete application.
The Places Autocomplete by Google Maps is very helpful as it allows a user to search for an address or specific location. I’ll combine the Places Autocomplete API with a Google Map using the Angular Google Maps (agm) module.
tl;dr
Check out the Plunker: https://plnkr.co/edit/LdKdSj?p=preview
Getting Started
The first thing to do is to install the Angulage Google Maps module as well as the Google Maps TypeScript declarations file using Node Package Manager (npm):
$ npm install @agm/core --save
$ npm install @types/googlemaps --save-dev
Next, modify your @NgModule
decorator:
import { AgmCoreModule } from '@agm/core';
@NgModule({
imports: [
AgmCoreModule.forRoot({
apiKey: "YOUR KEY GOES HERE",
libraries: ["places"]
}),
BrowserModule,
FormsModule,
ReactiveFormsModule
],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule() {}
To review the code above:
- First, we import the
AgmCoreModule
class from the @agm/core module. - In our the
imports
property we use theAgmCoreModule.forRoot()
method and specify the places library. - You will notice that I am also using
ReactiveFormsModule
for Angular’s new model-driven forms.
Create Template
For this tutorial I am going to set the styles and template properties in my @Component
decorator configuration.
I would suggest that you have an external Sass file and an external HTML template instead.
Let’s take a look at the @Component
configuration:
@Component({
selector: 'my-app',
styles: [`
agm-map {
height: 300px;
}
`],
template: `
<div class="container">
<h1>Angular 2 + Google Maps Places Autocomplete</h1>
<div class="form-group">
<input placeholder="search for location" autocorrect="off" autocapitalize="off" spellcheck="off" type="text" class="form-control" #search [formControl]="searchControl">
</div>
<agm-map [latitude]="latitude" [longitude]="longitude" [scrollwheel]="false" [zoom]="zoom">
<agm-marker [latitude]="latitude" [longitude]="longitude"></agm-marker>
</agm-map>
</div>
`
})
export class App implements OnInit {}
A couple of things to note:
- Don’t forget that you must define a height for the map element. In this case, I am setting it to 300px.
- For the template, I am using some minimal Bootstrap CSS styling to make things look nice.
- My input element uses a local template variable named
search
. This is declared using the hash symbol ( # ), or I could have used theref-
prefix such asref-search
. - My input element has one-way data binding for the
FormControl
directive to thesearchControl
publicly available variable in my controller. We’ll look at this in a moment. - Next, I am implementing the
agm-map
directive as per the documentation for the Angular Google Maps module. I am binding the latitude to a public variable in my controller namedlatitude
, and I am doing the same thing for the longitude. I am also setting thescrollwheel
option to false. Then I bind the zoom value to myzoom
property in my controller. - I have also included a marker that will use the same longitude and latitude as the center point for the map.
Implement Controller
Next, we need to implement our controller. Let’s take a look at it:
import { ElementRef, NgZone, OnInit, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { } from 'googlemaps';
import { MapsAPILoader } from '@agm/core';
export class App implements OnInit {
public latitude: number;
public longitude: number;
public searchControl: FormControl;
public zoom: number;
@ViewChild("search")
public searchElementRef: ElementRef;
constructor(
private mapsAPILoader: MapsAPILoader,
private ngZone: NgZone
) {}
ngOnInit() {
//set google maps defaults
this.zoom = 4;
this.latitude = 39.8282;
this.longitude = -98.5795;
//create search FormControl
this.searchControl = new FormControl();
//set current position
this.setCurrentPosition();
//load Places Autocomplete
this.mapsAPILoader.load().then(() => {
let autocomplete = new google.maps.places.Autocomplete(this.searchElementRef.nativeElement, {
types: ["address"]
});
autocomplete.addListener("place_changed", () => {
this.ngZone.run(() => {
//get the place result
let place: google.maps.places.PlaceResult = autocomplete.getPlace();
//verify result
if (place.geometry === undefined || place.geometry === null) {
return;
}
//set latitude, longitude and zoom
this.latitude = place.geometry.location.lat();
this.longitude = place.geometry.location.lng();
this.zoom = 12;
});
});
});
}
private setCurrentPosition() {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
this.latitude = position.coords.latitude;
this.longitude = position.coords.longitude;
this.zoom = 12;
});
}
}
}
First, I import the necessary classes: NgZone
, OnInit
, ViewChild
, FormControl
and MapsAPILoader
.
Inside the class we first define our public variables to store the latitude
, longitude
, zoom
and our searchControl
.
Next, I am using the @ViewChild
decorator to get access to the DOM input
element. The @ViewChild
decorator accepts a single string that is the selector to the element or directive.
In this case I am referencing the local template variable #search
. It decorates the variable searchElementRef
, which is an ElementRef
to the search input.
Next, I use dependency injection (DI) to inject the MapsAPILoader
and NgZone
dependencies.
We’ll use the MapsAPILoader
later on in the ngOnInit()
method to load the Google Places API.
The NgZone
service enables us to perform asynchronous operations outside of the Angular zone, or in our case, to explicitly run a function within the Angular zone.
Angular’s zones patch most of the asynchronous APIs in the browser, invoking change detection when an asynchronous code is completed.
As you might expect Angular zones are not patching the asynchronous behavior of Google Place autocomplete.
Now, in the ngOnInit()
method I set some initial values for the latitude
, longitude
and zoom
.
Then, I create a new FormControl()
instance for the searchControl
. Next I invoke the setCurrentPosition()
method, which will simply attempt to use the geolocation API in the browser to set the map to the user’s current location. You can just ignore this if you want.
I then use the load()
method in the MapsAPILoader
to load the Google Places API.
This returns a promise object, so when this has been resolved we can then fire up our Google Places Autocomplete.
The code here should look familiar to the code sample available from Google.
Note: I am limiting the results for Google Places Autocomplete to addresses using the types
option.
If you want to show all results, simply remove this, or specify an alternative type.
Finally, we use the fat arrow function in TypeScript to attach an event handler to the place_changed event for our autocomplete.
We then wrap our asynchronous code within the NgZone.run()
method.
The documentation for NgZone
indicates that:
Running functions via run allows you to reenter Angular zone from a task that was executed outside of the Angular zone
Within the run()
method we will store the data returns from the Google Maps Places API, including updating our map’s latitude and longitude.
Without the use of the NgZone.run()
method the changes to our latitude
, longitude
and zoom
will not be triggered until change detection is triggered by another event or asynchronous operation.
Demo
Here is a demo on Plunker: