Search code examples
javascriptgoogle-mapsgoogle-maps-api-3

Manipulate markers in Google Maps Javascript API v3


I am really struggling with the structure of the new Google Maps Javascript API.

In the tutorial for adding a map with a marker, the new structure wants to async load the library, to load the relevant class(es):


  const { Map } = await google.maps.importLibrary("maps");
  const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");

OK, so I wrap this in some sort of async method to init the map


const initMap = async () => {
  const { Map } = await google.maps.importLibrary("maps");
  const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
  // ...
}

No problem.. but what happens if I want another method to add or manipulate a marker? Do I call importLibrary again?


const addMarker = async (blah) => {
  // Will this hit some sort of local cache of types??
  const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
  // ...
}

I mean.. is this thing cached, or is it going to try to import code from the interweb every time? Why do I have to async load just to get a class definition?

There is no top-level await in the browser (yet), so the load of these classes needs to be scoped to a function, which means they are not accessible outside that scope, like.. another function.

How are people doing this? The whole structure of this API seems bonkers to me.

What I want to do is something like this:


// This won't work, but this is what I want to do:
const { Map } = await google.maps.importLibrary("maps");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");

class GoogleMap {

  constructor() {
    this._map;
    this._markers = [];
  }

  init(elem) {
    this._map = new Map(elem, {...});
  }

  addMarker(position) {
    this._markers.push(new AdvancedMarkerElement({
      this._map,
      position: position,
    }));
  }
}

What is the best-practice guidance for using this [bonkers] API in this way?


Solution

  • Admittedly, the new Marker API is a bit dorky.

    Still, the dynamic loading scripts are indeed cached. You can verify this by checking the Network tab and looking for https://maps.googleapis.com/maps-api-v3/api/js/.../marker.js:

    const { Map } = await google.maps.importLibrary("maps");
    let AdvancedMarkerElement;
    ({ AdvancedMarkerElement } = await google.maps.importLibrary("marker"));
    
    // some logic 
    
    ({ AdvancedMarkerElement } = await google.maps.importLibrary("marker"));
    ({ AdvancedMarkerElement } = await google.maps.importLibrary("marker"));
    

    Secondly, by calling .importLibrary(), you're accessing the global google.maps namespace. Calling importLibrary("marker") then populates the google.maps.marker library. That's all there's to it.

    Once loaded, these two calls are equivalent:

    const m1 = new google.maps.marker.AdvancedMarkerElement(...);
    const m2 = new AdvancedMarkerElement(...);
    

    That said, you can write your own utility that wraps around the google.maps.marker library to avoid the verbose dot paths…

    And this brings me to an official utility package (@googlemaps/adv-markers-utils) that's poised to "simplify common patterns for using Advanced Markers."

    With it, you only import the marker library once (but it's got to be in an async context):

    async function main() {
      const {Map} = await google.maps.importLibrary('maps');
      
      // Notice that we don't care about the return value of this statement -- it only populates `google.maps.marker`
      await google.maps.importLibrary('marker');
    }
    

    Afterwards, you are free to use the synchronous initializer throughout your app:

    const marker = new Marker({
      position: {lat: 53.5, lng: 10.05},
      map
    });
    

    Do check out the related playground too.