Gopikrishna S
Gopikrishna S

Reputation: 2441

Cant access JavaScript object property

I can't access the JavaScript object's property even though the property is there. I am trying to access the Factory object's property in AngularJS. Below is the code:

angular.module("appManager")

  // factories.js
  .factory("DataFactory", function ($http) {
    var fac = {};

    fac.expenses = {};
    $http.get("mvc/m/revenue.json")
        .success(function (result) {
            console.log(result);            // line 9
            fac.expenses = result.expenses;
        });
    console.log(fac);                       // line 13
    return fac;
  })

  // expenseController.js
  .controller("expenseCtrl", function($scope, DataFactory){
    $scope.data = DataFactory.expenses;
    console.log(DataFactory);               // line 4
    console.log(DataFactory.expenses);      // line 5
    console.log($scope.data);               // line 6

    // Global // var d = {};
    d = DataFactory;
    e = DataFactory.expenses;
  });

And the ouput in the console: enter image description here

Someone also asked (here) about this but he did not get a solution. I also tried what was suggested there: using keys, the console throws "undefined function" error. You can also notice that the log() // 9 inside the log inside $http is called later than other function; can this be the reason? But notice the log() // 13 can already print the object.

I thought there may be something wrong with AngularJS, and I tried some global variables d and e, for testing purpose, which also provide the same result.

I also tried subscripts: DataFactory["expenses"] with same result

Can someone please tell me what I am doing wrong?

Upvotes: 0

Views: 2628

Answers (2)

Martin
Martin

Reputation: 16300

jfriend00 hit the nail on the head. You are returning an empty object from your DataFactory service, and then getting a result from the Ajax service. The nature of asynchronous calls is that your code continues to run, returning the empty object, while it waits for the response from the server. Async bites everyone the first time they run into it, but then you will recognise the pattern in the future.

There is a modern way of dealing with this called the Deferred Promise pattern. The $q service in Angular provides the deferred object for you.

https://docs.angularjs.org/api/ng/service/$q

So your service with $q would look something like this:

 .factory("dataFactory", ['$http', '$q', function ($http, $q) {
     var fac = $q.defer();

     $http.get("mvc/m/revenue.json")
         .success(function (result) {
             fac.resolve(result);
         });                   
      return fac.promise;
 }]);

and then your controller would look like

 .controller("expenseCtrl", ['$scope', 'dataFactory', function($scope, dataFactory){
    dataFatory.then(function(data) {
        $scope.data = data.expenses;
    });
 }]);

Upvotes: 1

jfriend00
jfriend00

Reputation: 708206

You are using asynchronous calls, but trying to treat them like they happen synchronously. That simply doesn't work as you end up trying to use a result before the networking call that requested that result has actually finished and been returned.

Look at the order of the console statements. line 13 (after the $http.get()) call happens BEFORE line 9 (in the .success handler of the $http.get()). So, at line 13, there's no way that fac.expenses is assigned yet. Even the ones labelled lines 4,5,6 happen BEFORE fac.expenses is set after line 9.

It appears that you aren't writing your code to work with Asynchronous networking calls and that is why it isn't working properly. You can ONLY reliably use fac.expenses from within the .success handler or something that is called from the .success handler.

Also, since this is a timing related issue, you can be fooled in numerous ways by debugging tools. In general, you should log the actual value you're looking for, not a parent object because console.log() may confuse you when you later trying to drill into the parent object in the log (because it has since been filled in).


I don't quite follow exactly what you're trying to achieve here to know exactly what to recommend, but if you want to use fac.expenses, then you must either use it inside of the .success handler where it was initially returned or you can call some other function and pass fac.expenses to that other function. You can't return it from an async callback like you're doing and expect it to be available in other chained code that may have already executed.

Upvotes: 2

Related Questions