5

Now that I have found a way to initialize Google Maps with the help of Andy Joslin in this SO initialize-google-map-in-angularjs, I am looking for a way to asynchronous load a Google Map Object.

I found an example of how to do this in the phonecat project.

Notice how the JS files are loaded in this example: index-async.html

In my Jade Scripts partial that is loaded into my program I tried:

script(src='js/lib/angular/angular.js')
script(src='js/lib/script/script.min.js')

script
  $script([
    'js/lib/angular/angular-resource.min.js',
    'js/lib/jquery/jquery-1.7.2.min.js',
    'http://maps.googleapis.com/maps/api/js?key=AIzaSyBTmi_pcXMZtLX5MWFRQgbVEYx-h-pDXO4&sensor=false',
    'js/app.js',
    'js/services.js',
    'js/controllers.js',
    'js/filters.js',
    'js/directives.js',
    'bootstrap/js/bootstrap.min.js'
    ], function() {
      // when all is done, execute bootstrap angular application
      angular.bootstrap(document, ['ofm']);
    });

When I do this and go to load the map page I get:

A call to document.write() from an asycrononously-loaded 
external script was ignored.

This is how Google Maps is being loaded now as a service:

'use strict';

var app = angular.module('ofm.services', []);

app.factory('GoogleMaps', function() {

  var map_id  = '#map';
  var lat     = 46.87916;
  var lng     = -3.32910;
  var zoom    = 15;
  var map     = initialize(map_id, lat, lng, zoom);

  return map;
});

function initialize(map_id, lat, lng, zoom) {
  var myOptions = {
    zoom : 8,
    center : new google.maps.LatLng(lat, lng),
    mapTypeId : google.maps.MapTypeId.ROADMAP
  };
  return new google.maps.Map($(map_id)[0], myOptions);
}

It appears that this should be returning a promise from what I recall reading. But this AngularJS is very new to me.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Larry Eitel
  • 1,397
  • 5
  • 19
  • 37
  • To promote progress on this I created a git project here: https://github.com/LarryEitel/angular-google-maps AND pushed it live here: http://angular-google-maps.nodester.com/. I started a thread in Google Groups on this here: https://groups.google.com/forum/?fromgroups&nomobile=true#!topic/angular/CM8ewcWeTF4 – Larry Eitel Jun 27 '12 at 15:05
  • When you load the Maps API library asynchronously, you *must* provide a callback function with the `callback=` query parameter. Otherwise the API loader will use `document.write()` which doesn't work from an asynchronous call. The mini-library in [GFoley83](http://stackoverflow.com/a/17396353/1202830)'s answer adds this parameter for you, which is why it can work in an asynchronous loading situation like this. – Michael Geary Jul 01 '13 at 01:32

3 Answers3

7

here's my solution I came up without using jQuery: (Gist here)

angular.module('testApp', []).
    directive('lazyLoad', ['$window', '$q', function ($window, $q) {
        function load_script() {
            var s = document.createElement('script'); // use global document since Angular's $document is weak
            s.src = 'https://maps.googleapis.com/maps/api/js?sensor=false&callback=initialize';
            document.body.appendChild(s);
        }
        function lazyLoadApi(key) {
            var deferred = $q.defer();
            $window.initialize = function () {
                deferred.resolve();
            };
            // thanks to Emil Stenström: http://friendlybit.com/js/lazy-loading-asyncronous-javascript/
            if ($window.attachEvent) {  
                $window.attachEvent('onload', load_script); 
            } else {
                $window.addEventListener('load', load_script, false);
            }
            return deferred.promise;
        }
        return {
            restrict: 'E',
            link: function (scope, element, attrs) { // function content is optional
            // in this example, it shows how and when the promises are resolved
                if ($window.google && $window.google.maps) {
                    console.log('gmaps already loaded');
                } else {
                    lazyLoadApi().then(function () {
                        console.log('promise resolved');
                        if ($window.google && $window.google.maps) {
                            console.log('gmaps loaded');
                        } else {
                            console.log('gmaps not loaded');
                        }
                    }, function () {
                        console.log('promise rejected');
                    });
                }
            }
        };
    }]);
Neil S
  • 2,304
  • 21
  • 28
5

If you using jQuery in your AngularJS app, check out this function which returns a promise for when the Google Maps API has been loaded:

https://gist.github.com/gbakernet/828536

I was able to use this in a AngularJS directive to lazy-load Google Maps on demand. Works a treat:

angular.module('mapModule') // usage: data-google-map
    .directive('googleMap', ['$window', function ($window) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                // If Google maps is already present then just initialise my map
                if ($window.google && $window.google.maps) {
                    initGoogleMaps();
                } else {
                    loadGoogleMapsAsync();
                }

                function loadGoogleMapsAsync() {
                    // loadGoogleMaps() == jQuery function from https://gist.github.com/gbakernet/828536
                    $.when(loadGoogleMaps())
                        // When Google maps is loaded, add InfoBox - this is optional
                        .then(function () {
                            $.ajax({ url: "/resources/js/infobox.min.js", dataType: "script", async: false });
                        })
                        .done(function () {
                            initGoogleMaps();
                        });
                };

                function initGoogleMaps() {
                    // Load your Google map stuff here
                    // Remember to wrap scope variables inside `scope.$apply(function(){...});`
                }
            }
        };
    }]);
GFoley83
  • 3,439
  • 2
  • 33
  • 46
  • 9
    Eek, mixed Angular and jQuery! – Beetroot-Beetroot Jul 01 '13 at 02:47
  • 1
    @Beetroot-Beetroot Bet your ass I did! Currently there's no easy way to load scripts with AngularJS and the Google Maps API doesn't support promises natively. I've proposed a more than adequate solution; if you've got a better one then lets see it! :) – GFoley83 Jul 01 '13 at 04:58
  • GFoley, I take your point and I didn't say this wasn't rational, I just said "eek". I've not tried but can AngularJS's $q not coerce jQuery promises into its own as per Q.js? If so, then in `loadGoogleMapsAsync()`, all but the `$.ajax(...)` expression can be written with $q, which seems more appropriate in an Angular.js module. – Beetroot-Beetroot Jul 01 '13 at 08:28
  • Also, should you not `return $ajax(...)`? Otherwise (a) `initGoogleMaps()` might as well be inside the `.then()` callback, and (b) the user could start interacting with the map before `infobox` is available. – Beetroot-Beetroot Jul 01 '13 at 08:34
  • @Beetroot-Beetroot I've not used `$q` at all as I'm still new to Angular though I'd imagine you could easily switch it out, yeah. Feel free to update my answer above if you find a working solution. As for your a & b, that's getting off topic. – GFoley83 Jul 01 '13 at 11:29
  • I'll see if I can find time to run some tests with $q. – Beetroot-Beetroot Jul 01 '13 at 12:09
2

Take a look of this i think its more reliable

    var deferred = $q.defer();
                        var script = document.createElement('script');

                        $window.initMap = function() {
                            //console.log("Map  init ");

                            deferred.resolve();
                        }
                        script.src = "//maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&libraries=places&callback=initMap";
                        document.body.appendChild(script);
                        return deferred.promise;
yonia
  • 1,681
  • 1
  • 14
  • 12