Jamie
Jamie

Reputation: 33

AngularJS object losing value inside controller method

I'm a junior dev, so I might be missing something obvious, but I'm feeling a bit loony. I have a simple Angular webapp. I'm attempting to load a hash-dictionary of environment names that correspond to arrays of hosts. {development: ["dev.8090", "host.dev.9009"]} and then use that dictionary to find which host I'm currently on. I should be able to pass the location.host variable to the getEnv method and find the correlating key that will tell me which environment I'm in.

The dictionary loads, but when I try to access it inside of the getEnv method, it reverts to an empty object. Not undefined, mind you, but empty. Here's my code:

var app = angular.module('app', ['ngResource', 'ui.bootstrap', 'ui.router']);

app.config(['$httpProvider', function ($httpProvider) {

    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];

}]);

function AppController($scope, $http) {
    window.MY_SCOPE = $scope;

    $scope.env = "Local";
    $scope.dict = {};

    $scope.loadDict = function() {
      $http.get('api/call/').
        success(function(data){
          for (env in data.environment) {
          // data.environment = array of objects
          // [
          // {hosts: ["host1", "host2"], name: "string1"},
          // {hosts: ["host1", "host2"], name: "string2"}
          // ]
            var key = data.environment[env].name;
            $scope.dict[key] = data.environment[env].hosts;
          }
          console.log($scope.envDict)
          // in the console:
          // Object {string1: Array[2], string2: Array[2]}
        }).error(function(data){
            console.error(data);
        })
    };

    $scope.getEnv = function(host) {
      for (key in $scope.dict) {
        // never gets this far because $scope.dict is now = {}
        for (value in $scope.dict[key]) {
          if ($scope.dict[key][value] === host) {
            $scope.env = key;
          }
        }
      }
    };

    $scope.loadDict();
    $scope.getEnv("host1");
}

I can manually call each of these methods and get the results I want from the console, using the MY_SCOPE variable. If I hard-code the dictionary, it works. If I console.log $scope.dict from anywhere in the code except from inside of the $scope.getEnv function, I get the result I expect. As soon as $scope.getEnv is involved, $scope.dict = {}.

I've tried hard-coding the keys into the dictionary. I've tried moving the definition around in the code. I've tried exporting the loadDict method into a factory. All to no avail. Ideas?

Upvotes: 2

Views: 4890

Answers (4)

Pramod Sharma
Pramod Sharma

Reputation: 376

You need to treat things asynchronously . success is an asynchronous callback while getEnv is synchronous. The solution in this case is to define a promise in loadDict and resolve it on success call. Then , in the controller getEnv method you would write code after promise is resolved: Roughly the code will be like this, I have not tested it, just wrote to give you idea:

$scope.loadDict = function() {
        var deferred = $q.defer(); // to define a promise
      $http.get('api/call/').
        success(function(data){
                   deferred.resolve(data);//resolve the promise on success
          }
         }).error(function(data){
            console.error(data);
        })
     return deferred.promise;//return promise
    };

    $scope.getEnv = function(host) {
       $scope.loadDict().then(
        function(data) {

          for (env in data.environment) {
          // data.environment = array of objects
          // [
          // {hosts: ["host1", "host2"], name: "string1"},
          // {hosts: ["host1", "host2"], name: "string2"}
          // ]
            var key = data.environment[env].name;
            $scope.dict[key] = data.environment[env].hosts;

            for (key in $scope.dict) {
        // never gets this far because $scope.dict is now = {}
        for (value in $scope.dict[key]) {
          if ($scope.dict[key][value] === host) {
            $scope.env = key;
          }
        }
      }
     });

    };

Upvotes: 0

Steve Lang
Steve Lang

Reputation: 559

$scope.getEnv() is being called before $http.get() has returned data. You need to call $scope.getEnv() within the $http.get().success() block, like so:

$scope.loadDict = function() {
    $http.get('api/call/').success(function (data) {
        for (env in data.environment) {
            var key = data.environment[env].name;
            $scope.dict[key] = data.environment[env].hosts;
        }
        $scope.getEnv("host1");
    }).error(function(data){
        console.error(data);
    });
};

Upvotes: 0

laurelnaiad
laurelnaiad

Reputation: 4558

Your problem is that you didn't deal with the fact that loadDict is async internally.

One way to solve this is to wait for it to complete by returning a promise from it and waiting for that promise to be resolved.

There are other ways to go about this, but this is probably one of the ways that is closest to what you already have:

// inject $q so you can make a promise
function AppController($scope, $http, $q) {
    window.MY_SCOPE = $scope;

    $scope.env = "Local";
    $scope.dict = {};

    $scope.loadDict = function() {
      // set up the deferred response
      var deferred = $q.defer();

      $http.get('api/call/').
        success(function(data){
          for (env in data.environment) {
          // data.environment = array of objects
          // [
          // {hosts: ["host1", "host2"], name: "string1"},
          // {hosts: ["host1", "host2"], name: "string2"}
          // ]
            var key = data.environment[env].name;
            $scope.dict[key] = data.environment[env].hosts;
          }
          console.log($scope.envDict)
          // in the console:
          // Object {string1: Array[2], string2: Array[2]}
          // all is well so resolve the promise
          deferred.resolve();
        }).error(function(data){
            console.error(data);
            // reject the promise
            deferred.reject(data);
        })

      return deferred.promise;
    };

    $scope.getEnv = function(host) {
      for (key in $scope.dict) {
        // never gets this far because $scope.dict is now = {}
        for (value in $scope.dict[key]) {
          if ($scope.dict[key][value] === host) {
            $scope.env = key;
          }
        }
      }
    };

    $scope.loadDict().then(
      function () {
        $scope.getEnv("host1");
      },
      function (err) {
        // whatever you want to do if the loadDict function failed to do its job
      }
    );
}

Upvotes: 0

link64
link64

Reputation: 1976

The $http.get call in $scope.loadDict is asynchronous. getEnv is getting called before your dictionary has been loaded. You need to call getEnv once that data has come back.

Have loadDict return the $http.getcall which will give you a promise. You can then chain on to that promise a success callback.

You should also put your $http calls in some sort of service to do it the 'angular' way :)

Try this instead:

$scope.loadDict = function() {
      return $http.get('api/call/').
        success(function(data){
          for (env in data.environment) {
         var key = data.environment[env].name;
            $scope.dict[key] = data.environment[env].hosts;
          }
          console.log($scope.envDict)
          // in the console:
          // Object {string1: Array[2], string2: Array[2]}
        }).error(function(data){
            console.error(data);
        })
    };

$scope.loadDict().then(function(result){
     $scope.getEnv("host1");
}

Upvotes: 1

Related Questions