Angular-google-maps: Using Geocoder with GoogleMapsAPIWrapper

Created on 1 Oct 2016  ·  42Comments  ·  Source: SebastianM/angular-google-maps

I saw in issue #139 you are providing the ability to access google.maps object via the getMap() function, which I assume is now the getNativeMap() function in GoogleMapsAPIWrapper. I've also read that other people have gotten it to work, but I can't find any documentation or examples on how to use the GoogleMapsAPIWrapper and Geocoder.

import { Component, OnInit } from '@angular/core';
import { LocationService } from '../../core/location.service';
import { GoogleMapsAPIWrapper } from 'angular2-google-maps/core';

declare var google: any;

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {

  constructor(private wrapper: GoogleMapsAPIWrapper) {
    this.wrapper.getNativeMap().then((m) => {
      console.log("test");
    });

  }

  ngOnInit() {
    // var address = "1045 mission street san francisco";

    // var geocoder = new google.maps.Geocoder();

    // var result = "";

    // geocoder.geocode({ 'address': address }, (results, status) => {
    //   var latitude = results[0].geometry.location.lat();
    //   var longitude = results[0].geometry.location.lng();
    //   console.log("lat: " + latitude + ", long: " + longitude);
    // });
  }
}

Right now I can't even get the console.log to print out "test". I'm not sure why. Also would the m variable be the equivalent of google.maps? So that I can then use m.Geocoder()?

I'm also not sure if I'm importing the GoogleMapsAPIWrapper correctly. Currently I'm importing it in the core.module, since the Angular 2 guidelines say to have services be in the core.module. "sebm-google-map" works for me without any issues, so I think the AgmCoreModule is imported correctly, I'm just not sure about how to use GoogleMapsAPIWrapper.

import {
    ModuleWithProviders, NgModule,
    Optional, SkipSelf
} from '@angular/core';

import { AgmCoreModule, GoogleMapsAPIWrapper } from 'angular2-google-maps/core';


import { FirebaseService, FirebaseServiceConfig } from './firebase.service';
import { LocationService } from './location.service';


@NgModule({
    imports: [AgmCoreModule.forRoot({apiKey: "blahblahapikey"}),],
    declarations: [],
    exports: [AgmCoreModule],
    providers: [FirebaseService, LocationService, GoogleMapsAPIWrapper]
})
export class CoreModule {

    constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
        if (parentModule) {
            throw new Error(
                'CoreModule is already loaded. Import it in the AppModule only');
        }
    }

    static forRoot(config: FirebaseServiceConfig): ModuleWithProviders {
        return {
            ngModule: CoreModule,
            providers: [
                { provide: FirebaseServiceConfig, useValue: config }
            ]
        };
    }
}

I've been able to get google.maps.Geocoder() to work via GMap from PrimeNG, but sometimes I get google is not defined errors. So I'm trying to use Sebastian's google map instead.

stale

Most helpful comment

How about this -

import { Injectable, NgZone } from '@angular/core';
import { GoogleMapsAPIWrapper } from 'angular2-google-maps/core';
import { MapsAPILoader } from 'angular2-google-maps/core';
import { Observable, Observer } from 'rxjs';

declare var google: any;

@Injectable()
export class GMapsService extends GoogleMapsAPIWrapper{ 
    constructor(private __loader: MapsAPILoader, private __zone: NgZone) {
        super(__loader, __zone);
    }

    getLatLan(address: string) {
        console.log('Getting Address - ', address);
        let geocoder = new google.maps.Geocoder();
        return Observable.create(observer => {
            geocoder.geocode( { 'address': address}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    observer.next(results[0].geometry.location);
                    observer.complete();
                } else {
                    console.log('Error - ', results, ' & Status - ', status);
                    observer.next({});
                    observer.complete();
                }
            });
        })
    }
}

All 42 comments

If you just need Geocoder(), you don't need to use getNativeMap().

I'm importing MapsAPILoader, so I can use it this way :

this.mapsAPILoader.load().then(() => {
    console.log('google script loaded');
    var geocoder = new google.maps.Geocoder();
});

But if you want to get the native map, you TestComponent must be loaded inside the google map component :

<sebm-google-map [latitude]="lat" [longitude]="lng">
    <app-test></app-test>
</sebm-google-map>

and you have to be sure that your instance of GoogleMapsAPIWrapper inside TestComponent is the same instance used by SebmGoogleMap

@gnujeremie thx for the tip to use MapsAPILoader. How do I go about importing it? Did you import it along with AgmCoreModule?

No, AgmCoreModule is imported only in my module using google map.
I directly import import { MapsAPILoader } from 'angular2-google-maps/core'; in my service.

How about this -

import { Injectable, NgZone } from '@angular/core';
import { GoogleMapsAPIWrapper } from 'angular2-google-maps/core';
import { MapsAPILoader } from 'angular2-google-maps/core';
import { Observable, Observer } from 'rxjs';

declare var google: any;

@Injectable()
export class GMapsService extends GoogleMapsAPIWrapper{ 
    constructor(private __loader: MapsAPILoader, private __zone: NgZone) {
        super(__loader, __zone);
    }

    getLatLan(address: string) {
        console.log('Getting Address - ', address);
        let geocoder = new google.maps.Geocoder();
        return Observable.create(observer => {
            geocoder.geocode( { 'address': address}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    observer.next(results[0].geometry.location);
                    observer.complete();
                } else {
                    console.log('Error - ', results, ' & Status - ', status);
                    observer.next({});
                    observer.complete();
                }
            });
        })
    }
}

In addition to the solution presented by @vintesh I had to update the lat and lng inside the NgZone to update the map.

clickSearchAddress() {
        this._mapsService.getLatLan(this.model.address)
            .subscribe(
            result => {
                // needs to run inside zone to update the map
                this._zone.run(() => {
                    this.model.lat = result.lat();
                    this.model.lng = result.lng();
                });
            },
            error => console.log(error),
            () => console.log('Geocoding completed!')
            );
    }

I still run into problems when trying to use the geocoder. Here's my code.

import { MapsAPILoader } from 'angular2-google-maps/core';
constructor(public mapsApiLoader: MapsAPILoader) {
    this.mapsApiLoader.load().then(() => {
      console.log('google script loaded');
      this.geocoder = new google.maps.Geocoder();
      console.log(this.geocoder);
    });

I get two errors:

Cannot find name 'google'.
and
rollup failed: 'MapsAPILoader' is not exported by node_modules/angular2-google-maps/core/index.js (imported by src/services/location.ts). For help fixing this error see https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module

@slooker you need to add declare var google: any; before the declaration of your constructor.

@gnujeremie That solves the google undefined problem, but it still tells me that MapsAPILoader isn't exported by angular2-google-maps.

@slooker I too faced the same issue and MapsAPILoader error is coming because "node_modules/angular2-google-maps/core/index.js" do not export MapsAPILoader .I have solved this issue. Insert following code in "node_modules/angular2-google-maps/core/index.js" file as follows :

var core_module_2 = require('./core.umd'); exports.MapsAPILoader = core_module_2.MapsAPILoader;

This works for me. Hope this will work for you also.

Do you guys have a working version? I get my addresses from a database and I need their coordinates in order to build a marker cluster. GeoCoder is defined in the constructor but is not accessible via the nginit method.

VinTesh, why does the service you listed say that google is undefined? What is the proper way tos et this up? Thanks

I'm facing the same google undefined problem. Any suggestions?

I'm using "@agm/core": "^1.0.0-beta.0"

Thanks

getCurrentPositionSuccessCallBack(position: Position) {
    const latitude = position.coords.latitude;
    const longitude = position.coords.longitude;

    const latLng: LatLngLiteral = {
      lat: latitude,
      lng: longitude
    };

    const geoCoder = new google.maps.Geocoder();

    geoCoder.geocode({ "location": latLng }, (results, status) => {

is my code and it works fine, but I am trying to write a unit test

fit('onGeoLocate should set the right location', () => {

    spyOn(navigator.geolocation, 'getCurrentPosition').and.callFake(function () {
      const position = { coords: { latitude: 32, longitude: -96 } };
      arguments[0](position);
    });
    component.onGeoLocate();

but it fails with

 Failed: google is not defined

Help please?

Hello "google undefined" friends. Did you try the tip of our colleague @gnujeremie?
This is working for me (Angular 2.2.3):

import { Injectable } from '@angular/core';
import { MapsAPILoader } from 'angular2-google-maps/core';
import { Observable } from 'rxjs/Observable';

declare var google: any;

@Injectable()
export class MapsService {
    constructor(private __loader: MapsAPILoader) {

    }

    getGeocoding(address: string) {
        return Observable.create(observer => {
            try {
                //at this point the variable google may be still undefined (google maps scripts still loading)
                //so load all the scripts, then...
                this.__loader.load().then(() => {
                    let geocoder = new google.maps.Geocoder();
                    geocoder.geocode({ address }, (results, status) => {

                        if (status === google.maps.GeocoderStatus.OK) {
                            const place = results[0].geometry.location;
                            observer.next(place);
                            observer.complete();
                        } else {
                            console.error('Error - ', results, ' & Status - ', status);
                            if (status === google.maps.GeocoderStatus.ZERO_RESULTS) {
                                observer.error('Address not found!');
                            }else {
                                observer.error(status);
                            }

                            observer.complete();
                        }
                    });
                });
            } catch (error) {
                observer.error('error getGeocoding' + error);
                observer.complete();
            }

        });
    }
}

Fabio, can you provide a sample of how this is called and used?

@DoubleExposure assuming you have a constructor like this

constructor(private ms: MapsService) {
}

you can call this in your code

this.ms.getGeocoding( '10 10th Street NE, Atlanta, GA 30309' ).subscribe(function (x) {
console.log(x.toString());
});

@FabioBentoLuiz @gnujeremie Hi 've tried your code and it's working great. Except the first call which allways fail. I've tried to make a first call in an ngOnInit but it does not seems to work.

Here my usage attempt

import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { Router, ActivatedRoute, ParamMap} from '@angular/router';

import 'rxjs/add/operator/switchMap';
import { Observable } from 'rxjs/Observable';

import { Place } from './../place';
import { PlaceService } from './../place.service';
import { MapsService } from './map.service';
import { AlertService } from './../AlertComponent/alert.service';


@Component({
  selector: 'app-detailview',
  templateUrl: './detail.component.html',
  styleUrls: ['./detail.component.css'], 
})


export class DetailComponent implements OnInit {

    place: Place;  
    lat: number = 51.678418;
    lng: number = 7.809007;

    ngOnInit() {
        let selectedId = this.route.paramMap.subscribe(
                (params: ParamMap)  =>  this.place = this.placeService.getPlaceById(+params.get('id'))
            );
        this.maps.getGeocoding('')
            .subscribe(
            result => console.log(result),
            error => console.log(error),
            () => console.log('Geocoding completed!')
            );
    }

    constructor(private placeService: PlaceService,
                private route       : ActivatedRoute,
                private location    : Location,
                private alert       : AlertService,
                private maps        : MapsService) {
    }

    clickSearchAddress() {
        let address = this.place.name + " " + this.place.address + " " + this.place.city + " " + this.place.postalCode;
        this.maps.getGeocoding(address)
            .subscribe(
            result => {
                    this.lat = result.lat();
                    this.lng = result.lng();
            },
            error => console.log(error),
            () => console.log('Geocoding completed!')
            );
    }

    back(){
        this.location.back();
    }

Hi @Canadadry ,
How the first call fails? Are you using the MapsAPILoader?

My map is supposed to center on the address.
So I have mapped the clickSearchAddress() on a button and It only moves the map on the second click. On the first click I only have the log : Geocoding completed!
Stranger thing, I have put the clickSearchAddress() call on the subscribe of route.paramMapin ngOnInit and there the function work on the first call.

I have tried waiting before starting to click to be sure google map script are loaded but it still require two clics.

The MapsService used is the one you provide.

Try it using NgZone as described here

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

Add to your constructor

constructor(private _zone: NgZone){
}

Then set lat and lng like this:

this.maps.getGeocoding(address)
            .subscribe(
            result => {
                    this._zone.run(() => {
                    this.lat = result.lat();
                    this.lng = result.lng();
            });

            },
            error => console.log(error),
            () => console.log('Geocoding completed!')
            );

Here is another thread that explain the reason of it.

Let me know if it works.

It seems to work, but I don't see why.
Thanks

I got it working with agm combining answers of @vintesh, @FabioBentoLuiz and @neilpennell.

google-maps.service.ts:

import { Injectable, NgZone } from '@angular/core';
import { GoogleMapsAPIWrapper } from '@agm/core';
import { MapsAPILoader } from '@agm/core';
import { Observable, Observer } from 'rxjs';

declare var google: any;

@Injectable()
export class GMapsService extends GoogleMapsAPIWrapper{ 
    constructor(private __loader: MapsAPILoader, private __zone: NgZone) {
        super(__loader, __zone);
    }

    getLatLan(address: string) {
        console.log('Getting Address - ', address);
        let geocoder = new google.maps.Geocoder();
        return Observable.create(observer => {
            geocoder.geocode( { 'address': address}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    observer.next(results[0].geometry.location);
                    observer.complete();                    
                } else {
                    console.log('Error - ', results, ' & Status - ', status);
                    observer.next({});
                    observer.complete();
                }
            });
        })
    }
}

shops.component.ts:

import { Component, NgZone } from '@angular/core';
import { GMapsService } from '../services/google-maps.service'

@Component({  
  templateUrl: 'shops.component.html',
  styleUrls: ['shops.component.css']
})

export class ShopsComponent {  
  constructor(private gMapsService: GMapsService, private __zone: NgZone ){}
  lat:number
  lng:number

  getAddress() {
    this.gMapsService.getLatLan('Andorra')
      .subscribe(
        result => {
            this.__zone.run(() => {
                this.lat = result.lat();
                this.lng = result.lng();
            })
        },
        error => console.log(error),
        () => console.log('Geocoding completed!')
      );
  }
}

shops.component.html:

<agm-map [latitude]="lat" [longitude]="lng"></agm-map>

shops.component.css:

agm-map {
    height: 300px;
}

Thanks guys!

@taneljoeaar do you know why I'm getting google is not defined
at MappingService.getLatLan

I mostly copy and paste your code.

Thanks a lot.

@beachjf use the @FabioBentoLuiz solution instead, to get the lat and long because variable " google" may be still undefined as google maps scripts still loading. it works for me.

Try
import { GoogleMapsAPIWrapper } from '@agm/core';
instead of
import { GoogleMapsAPIWrapper } from 'angular2-google-maps/core';
for latest angular

@taneljoeaar or try import { } from 'googlemaps'; in whatever the file you're getting the error at.
Make sure the types for googlemaps are installed. (npm install --save @types/googlemaps)

Well I know that is an old question, but I'd like to comment on this topic a code about get the location objects with name and type of the city, state and country. But, in my case, I was needing to get these names from the coordinates of the users. So, to do it I wrote this based on last comments:

import {MapsAPILoader} from '@agm/core';

...

constructor(private mapsAPILoader: MapsAPILoader) {
    // Get this 'res' from some source, like ionic native geolocation
    // Or, for test, create some object like:
    let res = {
        coords: {
            latitude: 40.826514,
            longitude: -73.914628
        }
    }
    this.codeLatLng(res.coords);
  }

...

codeLatLng(coords) {
    this.mapsAPILoader.load().then(() => {
        console.log('google script loaded');
        let latlng = new google.maps.LatLng({lat: coords.latitude, lng: coords.longitude});
        let geocoder = new google.maps.Geocoder();
        let location = {
          country: null,
          state: null,
          city: null
        };
        geocoder.geocode({
          'latLng': latlng
        }, (results, status) => {
          if (status == google.maps.GeocoderStatus.OK) {
            if (results[1]) {
              for (let i = 0; i < results[0].address_components.length; i++) {
                for (let b = 0; b < results[0].address_components[i].types.length; b++) {
                  if (results[0].address_components[i].types[b] == "country") {
                    location.country = !location.country ? results[0].address_components[i] : location.country;
                  } else if (results[0].address_components[i].types[b] == "administrative_area_level_1") {
                    location.state = !location.state ? results[0].address_components[i] : location.state;
                  } else if (results[0].address_components[i].types[b] == "administrative_area_level_2") {
                    location.city = !location.city ? results[0].address_components[i] : location.city;
                  }
                  if (location.city && location.state && location.country) {
                    break;
                  }
                }
              }

              console.log(location);
            } else {
              console.log("Results not available");
            }
          }
          else {
            console.log("Geocoder failed due to: ", status);
          }
        });
      }
    );
  }

To get the coordinates values, I used the ionic native geolocation.
The code came from an angular 2+ & Ionic project.

I have implemented the code as you show it but I am getting the following error:

ERROR Error: StaticInjectorError [MapsAPILoader]:
   StaticInjectorError [MapsAPILoader]:
     NullInjectorError: No provider for MapsAPILoader!
     at _NullInjector.get (core.js: 923)
     at resolveToken (core.js: 1211)
     at tryResolveToken (core.js: 1153)
     at StaticInjector.get (core.js: 1024)
     at resolveToken (core.js: 1211)
     at tryResolveToken (core.js: 1153)
     at StaticInjector.get (core.js: 1024)
     at resolveNgModuleDep (core.js: 10586)
     at _createClass (core.js: 10625)
     at _createProviderInstance $ 1 (core.js: 10597)

NOTE: I am using Angular 5

Can you please give me a hand.
Thank you.

@ronaldrenteria Did you placed

AgmCoreModule.forRoot({
      apiKey: '<YOUR-GOOGLE-API-KEY>'
    })

on your "imports" in app.modules.ts?
Are you using LazyLoading?
That error sometimes occurs when you forget to do some import or to do all of the requirements needed by the lib to work.
You can get more info on that page: Angular Maps - Getting Started.

Well I want to tell you, I already show the map and a marker, but what I want to do is convert some directions to latitude and longitude, but this has not been possible.

I do not have the import in the app.modules.ts, because each component is using its own module and if I'm using LazyLoading.

In this case I have a component called home.component.ts and this in turn has its own module called home.module.ts

Before using MapsAPILoader it already showed the map and the markers (example by placing the longitude and latitude manually), but now I want to read the addresses from the DB and get the longitude latitude.

Thank you.

maps.service.ts

import { Injectable, NgZone } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

// Manejo de Mapas
import { GoogleMapsAPIWrapper, MapsAPILoader } from '@agm/core';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';

declare var google: any;

@Injectable()
export class MapsService extends GoogleMapsAPIWrapper {
  constructor(private __loader: MapsAPILoader, private __zone: NgZone) {
    super(__loader, __zone);
  }

  getLatLan(address: string) {
    console.log('Getting Address - ', address);
    let geocoder = new google.maps.Geocoder();
    return Observable.create(observer => {
      geocoder.geocode({ address: address }, function(results, status) {
        if (status === google.maps.GeocoderStatus.OK) {
          observer.next(results[0].geometry.location);
          observer.complete();
        } else {
          console.log('Error - ', results, ' & Status - ', status);
          observer.next({});
          observer.complete();
        }
      });
    });
  }
}

home.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { homeRouting } from './home.routing';
import { SmartadminModule} from '../shared/smartadmin.module';
import { HomeComponent} from './home.component';
// 614iT
import { config_church } from './../shared/churchpro.config';
// Indispensable para manejar los mapas.
import { AgmCoreModule, MapsAPILoader } from '@agm/core';
@NgModule({
  imports: [
    CommonModule,
    homeRouting,
    SmartadminModule,
    AgmCoreModule.forRoot({
      apiKey: config_church.GOOGLE_API_KEY
    })
  ],
  declarations: [HomeComponent]
})
export class HomeModule {}

home.component.ts

import { Component, OnInit, NgZone } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
// Mapas 
import { MapsService } from '../shared/services/maps.service';
@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: []
})
export class HomeComponent implements OnInit {
  title: string = 'My first AGM project';
  lat: number = 4.6517056;
  lng: number = -74.1028404;
  zoom: number = 13;
  Glat: number;
  Glng: number;

  constructor(private _mapsService: MapsService, private __zone: NgZone) {}

  ngOnInit() {}

  getAddress() {
    this._mapsService.getLatLan('Andorra').subscribe(result => {
        this.__zone.run(() => {
          this.Glat = result.lat();
          this.Glng = result.lng();
        });
      }, error => console.log(error), () => console.log('Geocoding completed!'));
  }
}

Hi everybody

Maybe someone is facing a similar problem.

I need to customize the AGM library, i downloaded the code and included this code like a part of my project, therefore i add the agmCoreModule from local src folder in apps.module, like this.

import { AgmCoreModule } from '../agmLocalModule/core.module';

AgmCoreModule.forRoot({
apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}),

i have a page with markers generated in a ngFor loop. But i am having the next error.

```

[latitude]="ubicacion.posicion.latitud"
[longitude]="ubicacion.posicion.longitud"
[markerClickable]="true"
>

```

ERROR Error: Uncaught (in promise): ReferenceError: google is not defined
ReferenceError: google is not defined
at MarkerManager.webpackJsonp.102.MarkerManager.addMarker (marker-manager.ts:82)
at AgmMarker.webpackJsonp.291.AgmMarker.ngOnChanges (marker.ts:169)

Any idea what could be the problem

For all those who are getting 'google is undefined' error:

use this event 'mapReady' of agm-map element.
"https://angular-maps.com/api-docs/agm-core/components/AgmMap.html#mapReady"

Could someone come up with a working example?

Working example of geocoding service https://stackblitz.com/edit/angular-google-maps-demo-geocoding based on examples listed here.

Can I call mapsAPILoader.load() multiple times?

hi every body i had a little problem , am new to AGM and angular too , but the thing is i got the place_id , i wanted to change this to latlng object thats was it , any help is appriciated

@henoktsegaye basic principle here https://developers.google.com/maps/documentation/javascript/examples/geocoding-place-id
so you can use example from posted above, in a few words you need this.geocoder = new google.maps.Geocoder() and geocoder.geocode({'placeId': placeId}, callbackFunction(results, status)) but this will be after the script loaded

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@supruniuk thz GOD

there is any way to have typed Observable result ?

@xanscale Why not, just implement interface and use it for type checking
export interface Location {
lat: number;
lng: number;
}

@supruniuk i mean to typing google objects like results and status

As @SainiSK comment

For all those who are getting 'google is undefined' error:

use this event 'mapReady' of agm-map element.
"https://angular-maps.com/api-docs/agm-core/components/AgmMap.html#mapReady"

Sample:
(mapReady)="initializedGoogleMapGeocoder($event)"

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shedar picture shedar  ·  4Comments

stot3 picture stot3  ·  3Comments

maneesht picture maneesht  ·  3Comments

gnujeremie picture gnujeremie  ·  3Comments

dineshkumar20 picture dineshkumar20  ·  3Comments