0

I have an angular view that has a table of rows consisting of a select list and an text box. When a select list index is changed, I need to update the corresponding text box on the same row with a lookup value from the database. I am using ng-Change on the select list to call a $scope function that utilizes $http.get to make the call through an ActionMethod. I have tried this in a million ways, and finally was able to extract a value from the $http.get function by assigning it to a scope variable, but I only ever get the value of the previous lookup triggered by the selected index change, not the current one. How can I get a value real-time? I understand it is asynchronous, so I know the nature of the problem. How do I work around it? Current state of my .js:

$scope.EntityId = null; 
$scope.EntityNameChanged = function (item, block) {
    for (var i = 0; i < block.length; i++)
    {
        if (item.Value == block[i].Name.Value) {
            $scope.GetEntityId(item.Value);
            block[i].Id = $scope.EntityId;
        }
    } 
} 
$scope.GetEntityId = function(name) {
    $http.get("EntityId", { params: { EntityName: name } }).then(function success(response) {
        $scope.EntityId = response.data[0].Value;
    }); 
};
jack_c
  • 301
  • 1
  • 2
  • 7
  • Possible duplicate of [How do I return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Heretic Monkey Jul 20 '16 at 18:59

3 Answers3

0

To prompt Angular to update the value of the scope on its $watch loop, call $scope.apply() after assignment. This should bring you to the most recent value.

EDIT: This answer was wrong. It was a $http get request, which already uses $apply. What you need to do is put the request inside a factory and return a promise. Require the factory in your controller.

    app.factory('getData', function ($http, $q){

    this.getlist = function(){            
        return $http.get('mylink', options)
            .then(function(response) {
              return response.data.itemsToReturn;
            });            
    }
    return this;
});

app.controller('myCtrl', function ($scope, getData){
    app.getData()
      .then(function(bar){
         $scope.foo = bar;
       });
});
gcoreb
  • 181
  • 5
  • thank you very much for reply. $scope.apply() wasn't valid, but $scope.$apply() was. However, this created a cascade of $rootscope errors. – jack_c Jul 20 '16 at 18:53
0

Your GetEntityId function is not async, even though it makes an async request. by the time it sets $scope.EntityId, the for loop has already exited.

You can't actually queue up async calls like this, because each one of them is trying to share a value outside the loop that could be set by any other iteration, so one item in the loop might get another item's return value.

Instead, you should return the promise back to the loop, and perform your .then in the loop. Something like the following:

$scope.EntityNameChanged = function(item, block) {
  for (var i = 0; i < block.length; i++) {
    if (item.Value == block[i].Name.Value) {
      $scope.GetEntityId(item.Value)
        .then(function success(response) {
          block[i].Id = response.data[0].Value;
        });
    }
  }
}
$scope.GetEntityId = function(name) {
  return $http.get("EntityId", {
    params: {
      EntityName: name
    }
  });
};

(note this is untested, but should do what you expect).

Claies
  • 22,124
  • 4
  • 53
  • 77
  • Thank you very much for your reply. This is similar to what I had at first, and I get the same issue when I try this method: "Cannot set property 'Id' of undefined. It seems block is not available within $http.get function. – jack_c Jul 20 '16 at 18:52
  • hmm, `block` definitely should be available in the `.then`, if the `.then` is inside the loop; I might be able to work up a testable example later. – Claies Jul 20 '16 at 19:15
0

The GetEntityID function should return a promise

function GetEntityId(name) {
    //save httpPromise
    var p = $http.get("EntityId", { params: { EntityName: name } });

    //return derived promise
    return p.then(function onSuccess(response) {
        //return chained data
        return response.data[0].Value;
    }); 
};

Then use an IIFE in the for loop.

$scope.EntityNameChanged = function (item, block) {
    for (var i = 0; i < block.length; i++) {
        //USE IIFE to hold value of i
        (function IIFE(i) {
            if (item.Value == block[i].Name.Value) {
                //save promise
                var p = GetEntityId(item.Value);
                //extract value from promise
                p.then(function onSuccess(Value) {
                     block[i].Id = Value;
                });
            }
        })(i); 
    }
} 

Because the onSuccess function gets invoked asynchronously after the for loop completes, an IIFE closure is necessary to preserve the value of i until after the data is returned from the server.


georgeawg
  • 48,608
  • 13
  • 72
  • 95