0

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?

Community
  • 1
  • 1
Gopikrishna S
  • 2,221
  • 4
  • 25
  • 39
  • console.log will not print inherited properties – Ven Jun 21 '14 at 19:54
  • @Ven what is inherited properties? – Gopikrishna S Jun 21 '14 at 19:57
  • If `d.expenses` has data but `e` doesn't, that means that somewhere `d.expenses` (or `DataFactory.expenses`) is replaced with another object, so it becomes different than `e`. – Oriol Jun 21 '14 at 19:57
  • @Oriol d and e are used *nowhere* else, then how could they be overwritten? – Gopikrishna S Jun 21 '14 at 19:59
  • This looks like the very common issue of the timing of an ajax response. The results of an ajax response must be used from within the success callback or passed to a function called from there. They can't be used elsewhere. `fac.expenses` is not valid outside of the `.success` handler where you set it. The other places you're trying to look at it, it likely has not yet been set. – jfriend00 Jun 21 '14 at 20:03
  • @GopikrishnaS In the code you shown it's clear `e == d.expenses`. But when you use the console, `e` is empty but `d.expenses` isn't. So the property `expenses` of `d` must have become another object. – Oriol Jun 21 '14 at 20:05
  • @jfriend00, that is why I had declared it outside the function in first place and also the log at line 13 prints the value of the object correctly. – Gopikrishna S Jun 21 '14 at 20:13
  • But `fac.expenses` is not yet set in it. See my answer for details. – jfriend00 Jun 21 '14 at 21:05

2 Answers2

2

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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    Just got pinged by SO for 2.5K views and I can't believe I was this dumb 8 years ago lol. Promises!! @jfriend00 and others, thank you for being there to set me straight! – Gopikrishna S Jul 15 '22 at 14:08
1

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;
    });
 }]);
Martin
  • 15,820
  • 4
  • 47
  • 56