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.stack.imgur.com/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.stack.imgur.com/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

Jule Wolf
  • 221
  • 1
  • 3
  • 11
  • Can you post the output of `console.log(coords);` – Hackerman Oct 07 '16 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. – Jule Wolf Oct 07 '16 at 19:34
  • That is the output of `console.log(coords);`, are you sure...I just need that, please – Hackerman Oct 07 '16 at 19:35
  • Maybe `getLatLng` is assync and the coords some times don't has the returns yet. – BrTkCa Oct 07 '16 at 19:37
  • @Hackerman : Yes, this is the output of console.log(coords); . The output of console.log(coords[0]); is "undefined" LucasCosta : I thought about that too, but both should be working since console.log(coords); works every time and console.log(coords[0]) is in the next line – Jule Wolf Oct 07 '16 at 19:39
  • That output doesn't make any sense...can you post a picture please? – Hackerman Oct 07 '16 at 19:41
  • No makes sense the array output be valid but you can't to get some index value. Or the array is not valid, or has multiples variables as same name, or and more common issue assync cause. We can't help you without more details, like the complete response of `getLatLng `. – BrTkCa Oct 07 '16 at 19:45
  • See if this can help: http://stackoverflow.com/questions/14794428/how-do-you-return-a-latitude-and-longitude-array-using-the-google-javascript-v3 –  Oct 07 '16 at 19:46
  • @JuleWolf can you [edit] your question to include the entire output of the three lines with `console.log`? You can format it properly in the question rather than in a comment, and having all three outputs is more useful than just the one. – jonhopkins Oct 07 '16 at 19:48
  • 1
    better yet create a demo that replicates issue – charlietfl Oct 07 '16 at 19:51
  • flow is messed up because the geocoder is asynchronous – charlietfl Oct 07 '16 at 19:57
  • Often times what you see on console.log isn't actually what you're working with. To verify, stop the code by use the debugger; statement and check the console.log. – Scottie Oct 07 '16 at 20:05

1 Answers1

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; }
Jason Cust
  • 10,743
  • 2
  • 33
  • 45
  • 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. – Jule Wolf Oct 07 '16 at 20:44
  • @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. – Jason Cust Oct 07 '16 at 20:50
  • Thanks for the explanation. I will try it ASAP and will post the result of your solution. – Jule Wolf Oct 07 '16 at 21:02
  • @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. :) – Jason Cust Oct 08 '16 at 16:24
  • I had no access to my code the past two days but I will try your solution this afternoon and will post my results immediatly – Jule Wolf Oct 10 '16 at 09:01
  • @JuleWolf I think you completely misunderstood my answer. Your new code is still suffering from the same issue I was trying to highlight with abstracted code. `asyncPush` wasn't a solution but an example of a simple async operation that was equatable to the asnyc `geocodeAddress` method. Unfortunately I guess it didn't hit its mark for you. I would recommend learning about async operations to really help you out. – Jason Cust Oct 10 '16 at 20:20
  • Yes I think I got something wrong in your suggestion. But shouldnt the promise solve the issue I am facing ? Like making async functions sync with waiting until there is a valid result.. May it help if I set the promises resolve if-condition to something else ? – Jule Wolf Oct 11 '16 at 16:43
  • 1
    @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. – Jason Cust Oct 13 '16 at 19:19