0

I have got a strange problem with the JavaScript Geocode Api of Google Maps in AngularJS.

First of all I did some researches and I find out that I have to use the javascript callback function to get the formatted address out of a coordinate.

I implemented the callback function like this:

"use strict";

var getParcelApp = angular.module("getParcelApp", []);

function getStartAddress(latlng, callback) {
    var geocoder = new google.maps.Geocoder;

    geocoder.geocode({ 'location': latlng }, function(results, status) {
        if (status === google.maps.GeocoderStatus.OK) {
            callback(results[2].formatted_address); // HERE THE CALLBACK
        }
    });
};

var GetParcelAppController = function($scope, $http) {
    $http.get("/umbraco/surface/ParcelSurface/GetLatLngParcel")
        .success(function(result) {
            $scope.coordinates = result;
            $scope.getStartCoordinate();
        })
        .error(function(data) {
            console.log(data);
        });

    $scope.getStartCoordinate = function() {
        var latlng;

        $scope.listOfCoordinates = [];

        for (var i = 0; i < $scope.coordinates.length; i++) {
            latlng = { lat: $scope.coordinates[i].Latitude, lng: $scope.coordinates[i].Longitude };

            getStartAddress(latlng, function(formattedAddress) {
                console.log(formattedAddress); // HERE formattedAddress WORKS
                var coordinate = {
                    id: $scope.coordinates[i].Id,
                    waypointId: $scope.coordinates[i].WaypointId,
                    latitude: $scope.coordinates[i].Latitude,
                    longitude: $scope.coordinates[i].Longitude,
                    address: formattedAddress // HERE formattedAddress ISN'T WORKING
                };
                $scope.listOfCoordinates.push(coordinate);
            });
        }
        console.log($scope.listOfCoordinates);

    };
};

getParcelApp.controller("getParcelAppCtrl", GetParcelAppController);

As you may see I want an array of coordinate objects. But I really don't know why the console.log shows the right result and array is complete empty?

The Firefox console shows the following:

  • The Array:
    • Array [ ] getParcelController.js:45:9
  • The formatted addresses:
    • Mitte, Berlin, Deutschland getParcelController.js:34:17
    • Innenstadt, Köln, Deutschland getParcelController.js:34:17
    • Altstadt-Lehel, München, Deutschland getParcelController.js:34:17
    • Mitte, Berlin, Deutschland getParcelController.js:34:17
    • Innenstadt, Köln, Deutschland getParcelController.js:34:17

Do you have an idea why the console log is working properly and the array function isn't? If I write the array function outside of the callback function and without address: the array is created.

To be more detailed:

I want to show the formatted addresses of the coordinates in a select drop down box. This is defined in a html file as

<select id="getParcel" class="form-control"></select>

and i edited the getStartAddress call like the following:

$scope.getStartCoordinate = function() {
    var latlng;
    var selectBox = document.getElementById("getParcel");

    $scope.listOfCoordinates = [];

    for (var i = 0; i < $scope.coordinates.length; i++) {
        latlng = { lat: $scope.coordinates[i].Latitude, lng: $scope.coordinates[i].Longitude };

        getStartAddress(latlng, function(formattedAddress) {
            var coordinate = {
                id: $scope.coordinates[i].Id,
                waypointId: $scope.coordinates[i].WaypointId,
                latitude: $scope.coordinates[i].Latitude,
                longitude: $scope.coordinates[i].Longitude,
                address: formattedAddress
            };
            $scope.listOfCoordinates.push(coordinate);
            console.log($scope.listOfCoordinates);

            var selectElement = document.createElement("option");
            selectElement.textContent = coordinate.address;
            selectElement.value = coordinate.address;

            selectBox.appendChild(selectElement);
        });
    }

};

I am really not an expert in javascript but in my understanding the function in getStartAddress is then called when the callback data are fetched? So the select drop down box should be filled with the addresses when the callback is finished, isn't it?

6
  • 1
    Because getStartAddress is an asynchronous function and you are logging $scope.listOfCoordinates before it has fetched the data (and pushed it into the array). You have to do whatever you are doing inside the callback. Why Async JS programming "isn't working" is probably one of the most common questions on SO. Commented Sep 10, 2015 at 22:58
  • I know that geocoder.geocode is an asynchronous function, because of that I use the callback function in getStartAddress. But I don't expected that function(formattedAddress) is also asynchronous? Commented Sep 10, 2015 at 23:37
  • function(formattedAddress) is the callback to the async function - it only gets executed after the data has been fetched. Commented Sep 10, 2015 at 23:43
  • Okay, then console.log($scope.listOfCoordinates) should show some results if I move it inside the function(formattedAddress), right? I moved it right under $scope.listOfCoordinates.push(coordinate); but nothing is logged. Even not a undefined or empty array. Commented Sep 10, 2015 at 23:54
  • It's not possible, from the code you are showing, for your formattedAddress to be defined in your first console.log(formattedAddress) and not be stored in your coordinate object (which is then pushed onto your array). Commented Sep 11, 2015 at 11:44

2 Answers 2

1

You are dealing with async google.maps.Geocoder.geocode function inside a loop. For that purpose you might want to utilize $q.all which combines a number of promises into one which is only resolved when all the promises are resolved.

In your case let's first introduce a promise for getting an address:

$scope.getStartAddressPromise = function (latlng) {
    var deferred = $q.defer();
    getStartAddress(latlng, function (formattedAddress) {
        console.log(formattedAddress); // HERE formattedAddress WORKS
        var coordinate = {
            id: latlng.Id,
            waypointId: latlng.WaypointId,
            latitude: latlng.Latitude,
            longitude: latlng.Longitude,
            address: formattedAddress // HERE formattedAddress ISN'T WORKING
        };
        $scope.listOfCoordinates.push(coordinate);
        deferred.resolve();
    });
    return deferred.promise;
};

and then you could replace:

for (var i = 0; i < $scope.coordinates.length; i++) {
    latlng = { lat: $scope.coordinates[i].Latitude, lng: $scope.coordinates[i].Longitude };

    getStartAddress(latlng, function (formattedAddress) {
        console.log(formattedAddress); // HERE formattedAddress WORKS
        var coordinate = {
            //id: $scope.coordinates[i].Id,
            //waypointId: $scope.coordinates[i].WaypointId,
            latitude: $scope.coordinates[i].Latitude,
            longitude: $scope.coordinates[i].Longitude,
            address: formattedAddress // HERE formattedAddress ISN'T WORKING
        };
        $scope.listOfCoordinates.push(coordinate);
    });
}
console.log($scope.listOfCoordinates);

with:

var promises = [];

$scope.listOfCoordinates = [];
angular.forEach($scope.coordinates, function (coordinate) {
    var latlng = { lat: coordinate.Latitude, lng: coordinate.Longitude };
    promises.push($scope.getStartAddressPromise(latlng));
});

$q.all(promises).then(function () {
    console.log($scope.listOfCoordinates);
});

Complete example

Plunker

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, this is also a possibility. But a little bit more complicated then just change a for loop to a forEach function.
Great example of how to resolve multiple promises after all have completed - exactly what I was looking for.
0

Okay I solved that problem with a forEach instead of the simple for loop.

So the result is the following:

"use strict";

var getParcelApp = angular.module("getParcelApp", []);

function getStartAddress(latlng, callback) {
    var geocoder = new google.maps.Geocoder;

    geocoder.geocode({ 'location': latlng }, function(results, status) {
        if (status === google.maps.GeocoderStatus.OK) {
            callback(results[2].formatted_address);
        }
    });
};

var GetParcelAppController = function($scope, $http) {
    $http.get("/umbraco/surface/ParcelSurface/GetLatLngParcel")
        .success(function(result) {
            $scope.coordinates = result;
            $scope.getStartCoordinate();
        })
        .error(function(data) {
            console.log(data);
        });

    $scope.getStartCoordinate = function() {
        var latlng;
        var selectBox = document.getElementById("getParcel");

        $scope.listOfCoordinates = [];

        $scope.coordinates.forEach(function(point) {
            latlng = { lat: point.Latitude, lng: point.Longitude };

            getStartAddress(latlng, function (formattedAddress) {
                var coordinate = {
                    id: point.Id,
                    waypointId: point.WaypointId,
                    latitude: point.Latitude,
                    longitude: point.Longitude,
                    address: formattedAddress
                };
                $scope.listOfCoordinates.push(coordinate);

                var selectElement = document.createElement("option");
                selectElement.textContent = formattedAddress;
                selectElement.value = formattedAddress;

                selectBox.appendChild(selectElement);
            });
        });
    };
};

getParcelApp.controller("getParcelAppCtrl", GetParcelAppController);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.