angular-google-maps run function ONCE after initial map load
Asked Answered
I

2

7

using angular-google-maps to incorporate a google map into an app

I need a command that will run a function ONCE after initial map load is complete
- but only on the initial load, not after each map manipulation

I can't use idle or tilesloaded since these are fired after every movement...

The function I want to run needs to get map bounds to pull data off a server on initial page load - i want this to occur ONCE on initial load, then be a manual function using a refresh map-control
- if i use idle or tilesloaded to fire this it will pull server data every time a user moves the map.

Does anyone know how to fire a once off command to get map details (bounds etc) after initial map load ?

I've tried putting maps.getBounds() in the 2nd promise function but it doesn't work.

Note, I've got a fiddle working here - I just can't chain any more promises after the $scope.map controls / options etc are defined because they don't return a promise:

The code example in the docs doesn't show how to chain a promise after the $scope.map is defined.

html

<div class="angular-google-map-container" ng-controller="MainCtrl">
    <ui-gmap-google-map center="map.center" zoom="map.zoom" draggable="true" options="map.options" events="map.events" control="googlemap">
    </ui-gmap-google-map>
</div>

controller

myApp.controller('MainCtrl', function($scope, uiGmapGoogleMapApi) {
    uiGmapGoogleMapApi
    .then(function(maps){
        $scope.map = {
            center: {
                latitude: 37.7749295, 
                longitude: -122.4194155 
            },
            zoom: 12
            events: {
                tilesloaded: function (maps, eventName, args) {
                    myServiceFuntion(maps)    // this work fine but fires every time
                },
                dragend: function (maps, eventName, args) {
                    myServiceFuntion(maps)    // this work fine but fires every time
                },
                zoom_changed: function (maps, eventName, args) {
                    myServiceFuntion(maps)    // this work fine but fires every time
                }
            }
        }

        $scope.bounds = maps.getBounds()    // this gives me 'getBounds() not a function'
        myServiceFuntion(maps);    // this gives an error... ?
        return maps;            //no promise returned here so no chance to delay the function below
    })
    .then(function(maps){
        //is this where i need to put my function ?  doesn't delay on map load since no promise returned...
    });
});

Obviously the maps object returned by the uiGmapGoogleMapApi promise is completely different to the maps object returned by events like tilesloaded etc... quite confusing.

Also, the FAQ only indicates how to use tilesloaded to get the map instance - which doesn't work for reasons already described.

Idolah answered 31/1, 2015 at 1:38 Comment(0)
I
9

The 'correct' method I believe is to use the API IsReady feature by injecting the uiGmapIsReady service into the controller. See the documentation.

With the uiGmapIsReady promise it's then possible to pass the map to a function / service etc with code like:

uiGmapIsReady.promise()                     // this gets all (ready) map instances - defaults to 1 for the first map
.then(function(instances) {                 // instances is an array object
    var maps = instances[0].map;            // if only 1 map it's found at index 0 of array
    $scope.myOnceOnlyFunction(maps);        // pass the map to your function
});

it's also possible to iterate through the instances array to run functions on each map (if you have more than one map loaded in your page):

uiGmapIsReady.promise()                     // this gets all (ready) map instances - defaults to 1 for the first map
.then(function(instances) {                 // instances is an array object
    angular.forEach(instances, function(value, key) {
        var maps = value.map;
        $scope.myOnceOnlyFunction(maps);    // will apply this function to each map
    });
});

so then the whole controller would look like

myApp.controller('MainCtrl', function($scope, uiGmapGoogleMapApi, uiGmapIsReady) {
    uiGmapGoogleMapApi
    .then(function(maps){
        $scope.googlemap = {};
        $scope.map = {
            center: {
                latitude: 37.7749295, 
                longitude: -122.4194155 
            },
            zoom: 13,
            pan: 1,
            options: myAppServices.getMapOptions().mapOptions,
            control: {},
            events: {
                tilesloaded: function (maps, eventName, args) {
                },
                dragend: function (maps, eventName, args) {
                },
                zoom_changed: function (maps, eventName, args) {
                }
            }
        };
    });

    uiGmapIsReady.promise()                     // this gets all (ready) map instances - defaults to 1 for the first map
    .then(function(instances) {                 // instances is an array object
        var maps = instances[0].map;            // if only 1 map it's found at index 0 of array
        $scope.myOnceOnlyFunction(maps);        // this function will only be applied on initial map load (once ready)
    });

    $scope.myOnceOnlyFunction = function(maps){  // this will only be run once on initial load
        var center = maps.getCenter();           // examples of 'map' manipulation
        var lat = center.lat();
        var lng = center.lng();
        alert('I\'ll only say this once ! \n Lat : ' + lat + '\n Lng : ' + lng);
    };
});

jsfiddle


...not sure why this isn't mentioned in the FAQ: 'How do I access the map instance?' - or why using tilesloaded (which is thought to be unreliable) is suggested instead of idle or uiGmapIsReady... ?
Perhaps the FAQ question was really 'how do i access the map on a continual basis' ?


Idolah answered 3/2, 2015 at 7:5 Comment(3)
uiGmapIsReady isn't working anymore, with angular 1.4.8, and angular-google-maps 2.2.1Sabellian
uiGmapIsReady still works fine - it's just that more recent versions of angular-google-maps (ie since I originally posted the answer) need to have an additional plugin called angular-simple-logger included. I've updated the jsfiddle to the latest versions of all code.Idolah
i've been trying to run two maps on the same page. (a big one and a small one). I found that the above answer lead to my solution. however i had to specify the number of instances in the promise() like so... uiGmapIsReady.promise(2).then(function(instances) {...}); without specifying the number of promises, as soon as i put a second set of html for the second map, both maps would stop rendering completely.Frankiefrankincense
I
1

As Sal Niro point out in another answer - one option to get around the constant calling of tilesloaded or idle is to define a variable and then mark it as true the first time the events functions run.

It's a bit hackish and surprising that the API has no method to access the maps object after initial map load from within the controller ...but it works.

However - adding this answer since it's not practical to put your entire controller within a function (or condition) - and there are some functions you may want to be called continuously. So simply define the 'run once' variable within your controller prior to your map being called.

Solution:
Define a variable (here arbitrarily called "initialMapLoad") set to false or 0 :
var initialMapLoad = 0

Then, within your google maps events definition (such as tilesloaded, dragend or idle) you can put the functions you only want to run once, within a conditional :

if(initialMapLoad === 0) { 
    my_single_run_function(); 
    var initialMapLoad = 1
}

Don't forget to redefine the initialMapLoad variable to 1 or true once your function runs.

Example:

myApp.controller('MainCtrl', function($scope, uiGmapGoogleMapApi) {

    var initialMapLoad = 0;

    uiGmapGoogleMapApi
    .then(function(maps){
        $scope.map = {
            center: {
                latitude: 37.7749295, 
                longitude: -122.4194155 
            },
            zoom: 13,
            events: {
                tilesloaded: function (maps, eventName, args) {
                    // functions that run every time 
                    alert('I say this after every tile load');

                    if(initialMapLoad === 0){
                        // functions that run only once
                        alert('I only say this once' + maps.getBounds());
                        initialMapLoad = 1;
                    }
                },
            }
        };
    })
});

See a working example in this jsfiddle

Idolah answered 1/2, 2015 at 5:24 Comment(3)
this is essentially the exact same answer I gave you, so I'll just delete my answerMcgehee
actually it's not 'the exact same answer' - since in your answer you proposed wrapping the entire controller in a function (which is not practical for so many reasons). ...but yes the base concept of using a variable then changing to executed on first run is the same. In any case I proposed edits to your answer to make it acceptable and they were rejected by mods because it was too different - and it was suggested that I add my edits as a new answer.Idolah
I proposed wrapping your map loading code in a closure, not the entire controller. Here is the fiddle again for reference jsfiddle.net/f387jw61Mcgehee

© 2022 - 2024 — McMap. All rights reserved.