AngularJS - load google map script async in directive for multiple maps

I am currently trying to load multiple google maps on a single page. I don't want to include google map API script into the HTML code as I don't want the script to be loaded unless the maps are in the current page. I want my maps to be called inside a single directive that will also perform the google map API script lazy loading.

So I searched around and found a solution that I tweaked a bit, but my problem is that it will only load one map but not the others.

My HTML looks like this:

<div id="mapParis" class="google-map" lat="48.833" long="2.333"></div>
<div id="mapWashington" class="google-map" lat="38.917" long="-77.000"></div>
<div id="mapTokyo" class="google-map" lat="35.667" long="139.750"></div>

And the directive:

// Google Map
app.directive('googleMap', ['$window', '$q', function( $window, $q ) {
    function loadScript() {
        console.log('loadScript');

        // use global document since Angular's $document is weak
        var s = document.createElement('script');
        s.src = '//maps.googleapis.com/maps/api/js?sensor=false&language=en&callback=initMap';
        document.body.appendChild(s);
    }

    // Lazy loading of the script
    function lazyLoadApi(key) {
        console.log('lazyLoadApi');

        var deferred = $q.defer();

        $window.initMap = function () {
            deferred.resolve();
        };

        if ( $window.attachEvent ) {  
            $window.attachEvent('onload', loadScript); 
        } else {
            $window.addEventListener('load', loadScript, false);
        }

        return deferred.promise;
    }

    return {
        restrict: 'C', // restrict by class name
        scope: {
            mapId: '@id', // map ID
            lat: '@',     // latitude
            long: '@'     // longitude
        },
        link: function($scope, elem, attrs) {
            // Check if latitude and longitude are specified
            if ( angular.isDefined($scope.lat) && angular.isDefined($scope.long) ) {
                console.log('-----');

                // Initialize the map
                $scope.initialize = function() {
                    console.log($scope.mapId);

                    $scope.location = new google.maps.LatLng($scope.lat, $scope.long);

                    $scope.mapOptions = {
                        zoom: 6,
                        center: $scope.location
                    };

                    $scope.map = new google.maps.Map(document.getElementById($scope.mapId), $scope.mapOptions);

                    new google.maps.Marker({
                        position: $scope.location,
                        map: $scope.map,
                    });
                }

                // Check if google map API is ready to run
                if ( $window.google && $window.google.maps ) {
                    console.log('gmaps already loaded');

                    // Google map already loaded
                    $scope.initialize();
                } else {
                    lazyLoadApi().then(function () {
                        // Promised resolved
                        console.log('promise resolved');

                        if ( $window.google && $window.google.maps ) {
                            // Google map loaded
                            console.log('gmaps loaded');

                            $scope.initialize();
                        } else {
                            // Google map NOT loaded
                            console.log('gmaps not loaded');
                        }
                    }, function () {
                        // Promise rejected
                        console.log('promise rejected');
                    });
                }
            }
        }
    };

Here is a jsFiddle with 3 maps, you will see that only the last one is loaded: http://jsfiddle.net/5Pk8f/1/

I guess that I am doing something wrong with my scope or the way the promise is handled, but I am quite out of ideas for now...

Thanks! (and sorry for my not that good english)


Update (after answer)

As an update, here the the full solution I came up with: http://plnkr.co/edit/1NpquJ?p=preview (@maurycy plunker)

Google map service
// Lazy loading of Google Map API
app.service('loadGoogleMapAPI', ['$window', '$q', 
    function ( $window, $q ) {

        var deferred = $q.defer();

        // Load Google map API script
        function loadScript() {  
            // Use global document since Angular's $document is weak
            var script = document.createElement('script');
            script.src = '//maps.googleapis.com/maps/api/js?sensor=false&language=en&callback=initMap';

            document.body.appendChild(script);
        }

        // Script loaded callback, send resolve
        $window.initMap = function () {
            deferred.resolve();
        }

        loadScript();

        return deferred.promise;
    }]);
Google map directive
// Google Map
app.directive('googleMap', ['$rootScope', 'loadGoogleMapAPI', 
    function( $rootScope, loadGoogleMapAPI ) {  

        return {
            restrict: 'C', // restrict by class name
            scope: {
                mapId: '@id', // map ID
                lat: '@',     // latitude
                long: '@'     // longitude
            },
            link: function( $scope, elem, attrs ) {

                // Check if latitude and longitude are specified
                if ( angular.isDefined($scope.lat) && angular.isDefined($scope.long) ) {

                    // Initialize the map
                    $scope.initialize = function() {                                        
                        $scope.location = new google.maps.LatLng($scope.lat, $scope.long);

                        $scope.mapOptions = {
                            zoom: 12,
                            center: $scope.location
                        };

                        $scope.map = new google.maps.Map(document.getElementById($scope.mapId), $scope.mapOptions);

                        new google.maps.Marker({
                            position: $scope.location,
                            map: $scope.map,
                        });
                    }

                    // Loads google map script
                    loadGoogleMapAPI.then(function () {
                        // Promised resolved
                        $scope.initialize();
                    }, function () {
                        // Promise rejected
                    });
                }
            }
        };
    }]);
HTML use sample
<div id="mapParis" class="google-map" lat="48.833" long="2.333"></div>
<div id="mapWashington" class="google-map" lat="38.917" long="-77.000"></div>
<div id="mapTokyo" class="google-map" lat="35.667" long="139.750"></div>

Thanks again to maurycy

Answers


you have a problem here with promises and initialisation, i've made it cleaner for you

Apparently the jsfiddle has been removed so here is working plunker: http://plnkr.co/edit/1NpquJ?p=preview

js

here is a service for lazy load gmaps

app.service('lazyLoadApi', function lazyLoadApi($window, $q) {
  function loadScript() {
    console.log('loadScript')
    // use global document since Angular's $document is weak
    var s = document.createElement('script')
    s.src = '//maps.googleapis.com/maps/api/js?sensor=false&language=en&callback=initMap'
    document.body.appendChild(s)
  }
  var deferred = $q.defer()

  $window.initMap = function () {
    deferred.resolve()
  }

  if ($window.attachEvent) {
    $window.attachEvent('onload', loadScript)
  } else {
    $window.addEventListener('load', loadScript, false)
  }

  return deferred.promise
});

then the directive does what it should do, work only with map, don't load js files on any other logic


Need Your Help

Connect with iOS and Android clients to SockJS Backend

java android ios spring spring-websocket

I'm developing WebSocket messaging backend using Spring WebSockets, which uses SockJS + STOMP protocol. The reason why not to use plain WebSockets is because I will need to leverage security integr...