3

I'm programming a Webapplication in which coordinates have to be saved into a AngularJS service. I'm using Ionic Framework, JavaScript and HTML.

My problem is, that the array itself is getting displayed correctly while the single elements get put out as "undefined".

//Controller für die Map und ihre Funktionen
mapApp.controller('MapCtrl', function($scope, $ionicLoading, dataService) {

//Funktion zur Geocodierung von Adressen Eingabe String
var geocodeAddress = function(geocoder, resultsMap) {

  //Hole Textfeld Input aus Service
  var address = dataService.getAddress();
  //Geocode Funktion analog zu Google Maps API Beispiel
  geocoder.geocode({'address' : address}, function(results, status) {
    if (status === google.maps.GeocoderStatus.OK) {
      resultsMap.setCenter(results[0].geometry.location);
      resultsMap.setZoom(14);

      dataService.setLatLng(results[0].geometry.location.lat(), results[0].geometry.location.lng());

      var marker = new google.maps.Marker({
        map: resultsMap,
        position: results[0].geometry.location,
      });
      //onClick Info Fenster mit Adresse
      var infoString = "Dein Startpunkt: <br>" + results[0].formatted_address;
      var infoWindow = new google.maps.InfoWindow({
        content: infoString
      });
      marker.addListener("click", function() {
        infoWindow.open(resultsMap, marker);
      });
    }
    else {
      alert("Ups, da ist etwas schief gegangen ! Error:  " + status);
    }
  });
}

//Funktion zur Initialisierung der Map
//inklusive Geocoding und Erreichbarkeitspolygonen
var initialize = function() {
      //Route360° stuff
      r360.config.serviceKey = 'KADLJY0DAVQUDEZMYYIM';
      r360.config.serviceUrl = 'https://service.route360.net/germany/';

      var myLatlng = new google.maps.LatLng(48.383, 10.883);
      var mapOptions = {
        center: myLatlng,
        zoom: 8,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };
      var map = new google.maps.Map(document.getElementById("map"), mapOptions);

      var geocoder = new google.maps.Geocoder();
      var p = new Promise(function (resolve, reject) {

        geocodeAddress(geocoder, map);

        if(dataService.getLatLng()) {
          resolve("Success!");
        }
        else {
          reject("Failure!");
        }
      });
      //console.log(dataService.getLatLng());
      p.then(function() {
        //console.log(dataService.getLatLng());
        var coords = dataService.getLatLng();
        console.log(coords);
        console.log(coords[0]);
        console.log(JSON.stringify(coords, null, 4));

        var time = dataService.getTime();
        var move = dataService.getMove();
        var colorPolygonLayer = new GoogleMapsPolygonLayer(map);
        showPolygons(48.4010822, 9.987607600000047, colorPolygonLayer, time, move);
      });
      $scope.map = map;
}
ionic.Platform.ready(initialize);
});

//Funtkion zum Erstellen und Anzeigen der Erreichbarkeitspolygone
var showPolygons = function(lat, lng, polygonLayer, time, move) {
  //Setzen der Optionen für die Berechnung der Polygone
  var travelOptions = r360.travelOptions();

  //Lat-Lng bestimmen
  travelOptions.addSource({ lat : lat, lng : lng });

  //Zeitintervalle bestimmen
  travelOptions.setTravelTimes(time*60);

  //Fortbewegungsmittel bestimmen
  travelOptions.setTravelType(move);

  r360.PolygonService.getTravelTimePolygons(travelOptions, function(polygons) {
        polygonLayer.update(polygons);
  });
}

//Controller für die Daten
//Eigentlich nur Daten in Service schreiben onClick
mapApp.controller("DataCtrl", function($scope, dataService) {
  $scope.setData = function() {
    var address = document.getElementById("address").value;
    var time = document.getElementById("time").value;
    var move = $scope.move;
    dataService.setData(address,time,move);
  };
});

//Service um Daten zwischen Controllern zu benutzen
mapApp.service("dataService", function() {
  var address;
  var time;
  var move;
  var latlng = [];

  //Adresse, Zeit und Fortbewegungsmittel setzen
  this.setData = function(passed_address, passed_time, passed_move) {
    address = passed_address;
    time = passed_time;
    move = passed_move
  };
  this.setLatLng = function (lat, lng) {
    latlng.push(lat);
    latlng.push(lng);
  };
  //Getter
  this.getAddress = function() {
    return address;
  };
  this.getTime = function() {
    return time;
  };
  this.getMove = function () {
    return move;
  };
  this.getLatLng = function(){
    return latlng;
  }
})

The particular lines in my code sample are

console.log(coords);
console.log(coords[0]);
console.log(JSON.stringify(coords, null, 4));

!(https://i.sstatic.net/w0pUI.jpg)

Those are my return values.

As I said, console.log(coords) prints out the correct array but if I want to call console.log(coords[0]) it returns "undefined" (same as console.log(JSON.stringify(coords,null,4));)

Can someone explain me that issue or can (even better) give me a solution to it ?

Edit after @Jason Cust's suggestion:

var arr = [];
      var p = new Promise(function (resolve, reject) {
        geocodeAddress(geocoder, map);
        asyncPush(arr, dataService.getLatLng(), resolve);
      });

      p.then(function() {
        var a = getArr();
        console.log(a);
        console.log(a[0]);

        var time = dataService.getTime();
        var move = dataService.getMove();
        var colorPolygonLayer = new GoogleMapsPolygonLayer(map);
        showPolygons(48.4010822, 9.987607600000047, colorPolygonLayer, time, move);
      });
      function asyncPush(a, val, cb) {
        setTimeout(function() {
          a.push(val);
          cb();
        } , 0);
      }
      function getArr() {return arr; }

And this is the result:

! https://i.sstatic.net/oAhYq.jpg

I could not use asyncPush for each coordinate since they were undefined again, so I just added the entire arr Array to var a so now it is a array in an array and it seems to work. I can of course build a work-around to save the 3-dimensional array in a 2-dimensional one, but was that what you meant to do ?

Edit: Trying to save the 3-dim array into a 2-dim one returns undefined variables again

Solution Edit: So I could finally solve my problem by following this tutorial about promises: http://exploringjs.com/es6/ch_promises.html

The trick was to wrap the geocodeAddress function with a promise and call the .then function on the promise in my initialize function to make the two functions get called after each other. Here is my code:

var geocodeAddress = function(geocoder, resultsMap) {
  return new Promise(function(resolve, reject) {
    //Hole Textfeld Input aus Service
    var address = dataService.getAddress();
    //Geocode Funktion analog zu Google Maps API Beispiel
    geocoder.geocode({'address' : address}, function(results, status) {
      if (status === google.maps.GeocoderStatus.OK) {
        resultsMap.setCenter(results[0].geometry.location);
        resultsMap.setZoom(14);

        dataService.setLatLng(results[0].geometry.location.lat(), results[0].geometry.location.lng());

        var marker = new google.maps.Marker({
          map: resultsMap,
          position: results[0].geometry.location,
        });
        //onClick Info Fenster mit Adresse
        var infoString = "Dein Startpunkt: <br>" + results[0].formatted_address;
        var infoWindow = new google.maps.InfoWindow({
          content: infoString
        });
        marker.addListener("click", function() {
          infoWindow.open(resultsMap, marker);
        });
      }
      if(dataService.getLatLng()) {
        resolve("Success");
      }
      else {
        alert("Ups, da ist etwas schief gegangen ! Error:  " + status);
        reject("Failed");
      }
    });
  });
}

//Funktion zur Initialisierung der Map
//inklusive Geocoding und Erreichbarkeitspolygonen
var initialize = function() {
      //Route360° stuff
      r360.config.serviceKey = 'KADLJY0DAVQUDEZMYYIM';
      r360.config.serviceUrl = 'https://service.route360.net/germany/';

      var myLatlng = new google.maps.LatLng(48.383, 10.883);
      var mapOptions = {
        center: myLatlng,
        zoom: 8,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };
      var map = new google.maps.Map(document.getElementById("map"), mapOptions);
      var geocoder = new google.maps.Geocoder();

      geocodeAddress(geocoder, map)
      .then(function() {
        var coords = dataService.getLatLng();
        var time = dataService.getTime();
        var move = dataService.getMove();
        var colorPolygonLayer = new GoogleMapsPolygonLayer(map);
        showPolygons(coords[0], coords[1], colorPolygonLayer, time, move);
      });
      $scope.map = map;
}
ionic.Platform.ready(initialize);
});

Anyway, thank you very much @Jason Cust for your help, searching a solution for this without much JS knowledge gave me a huge headache.

Many regards, Julian

12
  • Can you post the output of console.log(coords); Commented Oct 7, 2016 at 19:33
  • []0: 52.520006599999991: 13.404953999999975length: 2__proto__: Array[0] controllers.js:68 undefined Not well formatted, sorry. This is a example output for two coordinates. Commented Oct 7, 2016 at 19:34
  • That is the output of console.log(coords);, are you sure...I just need that, please Commented Oct 7, 2016 at 19:35
  • Maybe getLatLng is assync and the coords some times don't has the returns yet. Commented Oct 7, 2016 at 19:37
  • 1
    better yet create a demo that replicates issue Commented Oct 7, 2016 at 19:51

1 Answer 1

3

This is due to the async nature of the program and for console.log itself.

Inside the promise p is a call to an async function geocodeAddress that is never checked for when it completes so the process continues on to the check for dataService.getLatLng() returning a value (which it does in the form of the empty array, which is truthy) which causes the resolve to be executed which in turn finally gets into the function block where the console.log lines are.

Due to the async behavior of console.log and the reference of using the array value returned from dataService.getLatLng when console.log(coords) finally prints to STDOUT it has the chance to print the values. But since console.log(coords[0]) grabs the immediate value it is undefined, which again is later printed to STDOUT. The same goes for the JSON output since that operation is sync.

Essentially what the code does now:

var arr = [];

var p = new Promise(function(resolve, reject) {
  asyncPush(arr, 'foo');
  
  if (getArr()) {
    resolve();
  }
});

p.then(function() {
  var a = getArr();
  
  console.log(a);
  console.log(a[0]);
  console.log(JSON.stringify(a, null, 2));
});

function asyncPush(a, val) {
  setTimeout(function() { a.push(val) }, 0);
}
  
function getArr() { return arr; }

Checking to make sure the async waits before continuing:

var arr = [];

var p = new Promise(function(resolve, reject) {
  asyncPush(arr, 'foo', resolve);
});

p.then(function() {
  var a = getArr();
  
  console.log(a);
  console.log(a[0]);
  console.log(JSON.stringify(a, null, 2));
});

function asyncPush(a, val, cb) {
  setTimeout(function() { a.push(val); cb(); }, 0);
}
  
function getArr() { return arr; }

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

8 Comments

Thanks for your answer ! Can you further explain the use of the function asyncPush(a, val, cb) ? I dont really understand why it does what it does and why that helps me with my problem.
@JuleWolf The asyncPush is equivalent to the geocodeAddress in that they both use an async call. By providing a mechanism for knowing when the async operation is completed (via the cb argument) the code calling to it will know when to continue its operation.
Thanks for the explanation. I will try it ASAP and will post the result of your solution.
@JuleWolf Were you able to resolve your issue? If so and this answer helped please remember to mark it as answered so other users will know. :)
@JuleWolf :) Your "Solution Edit" is effectively what I was demonstrating in my example code. By wrapping geocode in a promise and returning that promise, the calling function will now know when it is completed. Anyway, glad you finally got to the same spot. Good luck moving forward but I sill would recommend taking a deeper dive into learning about async programming and how promises help manage it.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.