18

Usually in a plain javascript site, I can use the following script to reference google maps api and set the callback function with initMap.

<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap"></script>

What I observed is the initMap function in the plain javascript site is under the window scope, and it can be referenced in the script parameter settings - ?callback=initMap, but once I write a component in angular2 with a component method called initMap, the initMap will be under the scope of my component. Then the async loading script I set up in the index will not be able to catch my component initMap method.

Specifically, I 'd like to know how to achieve the same thing in Angular2?

PS: I know there is an angular2-google-maps component available in alpha via npm, but it currently is shipped with limited capability, so I 'd like to know how to load it in an easier way without using another component so I can just use google maps api to implement my project.

Alex Chebotarsky
  • 305
  • 2
  • 13
Downhillski
  • 2,233
  • 2
  • 23
  • 35

4 Answers4

22

I see you don't want another component, but polymer has components that work well with google apis. I have angular2 code that uses the polymer youtube data api. I had help getting it setup. Here is the plunker that got me started. I think the hardpart is getting setup for that callback, I'm sure you can do it without polymer. The example shows the tricky part an angular service is used to hook everything up.

    const url = 'https://apis.google.com/js/client.js?onload=__onGoogleLoaded'

    export class GoogleAPI {
      loadAPI: Promise<any>
      constructor(){
        this.loadAPI = new Promise((resolve) => {
          window['__onGoogleLoaded'] = (ev) => {
            console.log('gapi loaded')
            resolve(window.gapi);
          }
          this.loadScript()
        });
        
      }
      
      doSomethingGoogley(){
        return this.loadAPI.then((gapi) => {
          console.log(gapi);
        });
      }
      
      loadScript(){
        console.log('loading..')
        let node = document.createElement('script');
        node.src = url;
        node.type = 'text/javascript';
        document.getElementsByTagName('head')[0].appendChild(node);
        
      }
    }
jeff
  • 3,043
  • 3
  • 26
  • 40
9

I came across this while trying to develop a progressive web app, i.e. where there was a possibility of not being online. There was an error in the code examples: onload in the google maps script should be callback. So my modification of user2467174 led to

map-loader.service.ts

const url = 'http://maps.googleapis.com/maps/api/js?key=xxxxx&callback=__onGoogleLoaded';

@Injectable()
export class GoogleMapsLoader {
    private static promise;

    public static load() {
        // First time 'load' is called?
        if (!GoogleMapsLoader.promise) {

            // Make promise to load
            GoogleMapsLoader.promise = new Promise( resolve => {

                // Set callback for when google maps is loaded.
                window['__onGoogleLoaded'] = (ev) => {
                    resolve('google maps api loaded');
                };

                let node = document.createElement('script');
                node.src = url;
                node.type = 'text/javascript';
                document.getElementsByTagName('head')[0].appendChild(node);
            });
        }

        // Always return promise. When 'load' is called many times, the promise is already resolved.
        return GoogleMapsLoader.promise;
    }
}

And then I have a component with

import { GoogleMapsLoader } from './map/map-loader.service';
constructor() {

    GoogleMapsLoader.load()
    .then(res => {
        console.log('GoogleMapsLoader.load.then', res);
        this.mapReady = true;
    })

And a template

<app-map *ngIf='mapReady'></app-map>

This way the map div is only put into the dom if online.

And then in the map.component.ts we can wait until the component is placed into the DOM before loading the map itself.

ngOnInit() {
    if (typeof google !== 'undefined') {
        console.log('MapComponent.ngOnInit');
        this.loadMap();
    }
}
Simon H
  • 17,952
  • 10
  • 57
  • 101
5

Just in case you'd like to make it a static function, which always returns a promise, but only gets the api once.

const url = 'https://maps.googleapis.com/maps/api/js?callback=__onGoogleMapsLoaded&ey=YOUR_API_KEY';
    
export class GoogleMapsLoader {
  private static promise;
  public static load() {

    // First time 'load' is called?
    if (!GoogleMapsLoader.promise) {

      // Make promise to load
      GoogleMapsLoader.promise = new Promise((resolve) => {

        // Set callback for when google maps is loaded.
        window['__onGoogleMapsLoaded'] = (ev) => {
          console.log('google maps api loaded');
          resolve(window['google']['maps']);
        };

        // Add script tag to load google maps, which then triggers the callback, which resolves the promise with windows.google.maps.
        console.log('loading..');
        let node = document.createElement('script');
        node.src = url;
        node.type = 'text/javascript';
        document.getElementsByTagName('head')[0].appendChild(node);
      });
    }

    // Always return promise. When 'load' is called many times, the promise is already resolved.
    return GoogleMapsLoader.promise;
  }
}

This is how you can get the api in other scripts:

GoogleMapsLoader.load()
  .then((_mapsApi) => {
    debugger;
    this.geocoder       = new _mapsApi.Geocoder();
    this.geocoderStatus = _mapsApi.GeocoderStatus;
  });
Alex Chebotarsky
  • 305
  • 2
  • 13
1

This is what I'm currently using:

loadMapsScript(): Promise<void> {
  return new Promise(resolve => {
    if (document.querySelectorAll(`[src="${mapsScriptUrl}"]`).length) {
      resolve();
    } else {
      document.body.appendChild(Object.assign(document.createElement('script'), {
        type: 'text/javascript',
        src: mapsScriptUrl,
        onload: doMapInitLogic();
      }));
    }
  });
}

See my more comprehensive instructions here

Alex Chebotarsky
  • 305
  • 2
  • 13
Stephen Paul
  • 29,903
  • 14
  • 75
  • 65
  • Can you provide a full example? And how would it work if I have 3rd party libraries dependent on google maps (i.e. js-map-label). Maybe [this](https://stackoverflow.com/questions/42378356) for reference – Mohammad Ali Nov 20 '17 at 17:41
  • @MohammadAli, I've provided a link in my answer. I'd suggest loading both the scripts in a route resolver. First load the maps script, and then load the 3rd party script. Something like: `loadGoogleMapsScript().switchMap(() => loadThirdPartyScript()));` – Stephen Paul Nov 21 '17 at 04:55
  • Thank you for the response. Google maps has the url so I can use above code, but how would I go about loadThirdPartyScript logic if 3rd party plugins were in node modules folder? – Mohammad Ali Nov 21 '17 at 16:12