Call Stimulus Function from Google Maps callback

Hi There,

Is there a way to call a stimulus function from the google maps url callback?

Something like:

https://maps.googleapis.com/maps/api/js?key={{env(‘GOOGLE_PLACES_API_KEY’)}}&libraries=places&callback=address#initMap”

In this case there is an initMap function in the address_controller.

Alternatively, I could just check that the API has loaded in the connect function and then call initMap, but just wanted to see if there was a way to utilize the URL callback.

I’ll be doing a fair amount of Stimulus/Google Maps, so I’m happy to share what I learn if anyone has questions.

Thanks for any help!

3 Likes

The best way to do this right now is to turn the Google Maps callback into a custom event, and then connect a controller action to listen for that event.

Something like this:

https://maps.googleapis.com/maps/api/js?...&callback=dispatchMapsEvent
window.dispatchMapsEvent = function(...args) {
  const event = document.createEvent("Events")
  event.initEvent("google-maps-callback", true, true)
  event.args = args
  window.dispatchEvent(event)
}
<div data-controller="address"
     data-action="google-maps-callback@window->address#initMap">
5 Likes

Wow. I had temporarily triggered a click on a hidden element.

This feels cleaner. Thank you!

Actually this won’t work because in my case event triggers faster than stimulus controller connects

So what I did is
in application.ts

import GoogleMapsCallback from "../util/google_maps_callback_helper";
let promiseResolver: ()=> void;
const promise: Promise<boolean> = new Promise((res) => {
promiseResolver = res;
});

(window as any).googleMapCallback = () => {
promiseResolver();
};

GoogleMapsCallback.init(promise);

Then in helper file google_maps_callback_helper

export default class GoogleMapsCallback {
    private static promise: Promise<boolean>;
    static init(promise: Promise<boolean>) {
        this.promise = promise;
    }

    static ready() {
        return this.promise;
    }
}

Then in map controller

import { Controller } from "stimulus";
import GoogleMapsCallback from "../util/google_maps_callback_helper";

export default class extends Controller {
static targets = ["canvas"];

private canvasTarget: HTMLDivElement;

connect() {
    GoogleMapsCallback.ready().then(() => {
        this.initGoogleMap();
    }).catch(() => {
        console.error("Google maps have not been loaded");
    });
}

initGoogleMap() {

With this I have promise waiting for window.googleMapCallback to be called and this promise is stored in my google_maps_callback_helper class, which gives actual state of promise to map controller. So in case where map loaded before controller I have in connect already resolved promise, otherwise promise will be resolved later on. Cheers

Here’s another way of initializing the map in either scenario of stimulus or google maps being loaded first

// application.js
function dispatchMapLoadedEvent() {
  if (typeof application === 'undefined') return;

  const mapController = application.getControllerForElementAndIdentifier(document.getElementById("map"), "map");
  if (mapController) mapController.initializeMap();
}

// mapController.js
initialize() {
  this.initializeMap();
}

initializeMap() {
  if (typeof google === 'undefined' || this.map) return;

   this.map = new google.maps.Map(document.getElementById("map"), {
  }
}