Angular http returns $$state object

34,845

Solution 1

JavaScript I/O is usually, including in this case asynchronous. Your getUser call returns a $q promise. In order to unwrap it you have to chain to it and then unwrap it as such:

angular.module("test.controllers",["account"])
.controller("TestCtrl",["$scope","users",
    function(a,u){
        u.getUser().then(function(user){
            a.user = user;
            console.log(a.user);
        });
}]);

If you're using routing you might also want to look into the resolve option in the route. Also see this related question.

Solution 2

angular.module("account").factory("users",["$http",
    function(a){
      var factObj = {
         user: null,
         getUser: function(){
            return a.get("/user/me").then(function(r){
                factObj.user = r.data;
            });
        }
      factObj.getUser();

      return factObj;
    };
  }
]);

angular.module("test.controllers",["account"])
.controller("TestCtrl",["$scope","users",
    function(a,u){
        a.userService = u;
}]);

In your view

<div ng-controller="TestCtrl as test">{{test.userService.user}}</div>

Edits based on comments, true this would not work with the controller or view as you probably had them, but this pattern works well and removes the need to have .then in every controller that may re-use the data.

Typically storing your model in the factory or service that deals with fetching the data makes for an easier path to getting the data into every view where you need it. Down side here is you'd be fetching the user when this factory is referenced instead of explicitly firing the getUser function from the controller. If you want to get around that you can store the promise from the first time it's requested in the getUser function and just return that promise for all subsequent calls if it's already set, this way you can call getUser multiple times without repeating the API request.

Share:
34,845
wdphd
Author by

wdphd

Updated on July 26, 2022

Comments

  • wdphd
    wdphd over 1 year

    I have the following factory defined:

    angular.module("account").factory("users",["$http",
        function(a){
          return {
             getUser: function(){
                return a.get("/user/me").then(function(r){
                    return r.data;
                });
            }
        };
      }
    ]);
    

    And my controller:

    angular.module("test.controllers",["account"])
    .controller("TestCtrl",["$scope","users",
        function(a,u){
            a.user = u.getUser();
            console.log(a.user);
    }]);
    

    Here's the console.log:

     d {$$state: Object, then: function, catch: function, finally: function} $$state: Object status: 1 value: Object user: Object__v: 0 _id: "54c1fg29f36e117332000005" temp: "1ce3793175e0b2548fb9918385c2de09"  __proto__: Object __proto__: Object __proto__: Object __proto__: Object
    

    The above code is returning a state object instead of the user object. But from the log, the state object has the user object within value. How do i get the user object? Or am i doing this completely wrong?

    I know the other way is to return the $http.get and call the then() method within controller. But I'll be using the user object frequently and if i'm calling the then() method in controller, its almost same as using the $http.get in controller instead of the factory.

  • Benjamin Gruenbaum
    Benjamin Gruenbaum about 9 years
    This would still fail - OP's issue is with the asynchronisity of the get not with where the data is stored.
  • shaunhusain
    shaunhusain about 9 years
    Benjamin I use this pattern in lots of working apps, it works fine so long as you reference the data through the factory in the controller. More thorough example here plnkr.co/edit/cqyeXB5WgKZynQZdvEYP?p=preview
  • Benjamin Gruenbaum
    Benjamin Gruenbaum about 9 years
    All you have to do to know this wouldn't work is read the code. a.get returns a Promises/A+ compliant promise - it must in order to be an A+ promise defer execution to the next cycle. You will call getUser which will return a promise for nothing and then call .user which will return null. If you experience any other behavior please file a bug against the issue tracker. The behavior I'm describing is well specified.
  • wdphd
    wdphd about 9 years
    But if i'm using then() in controller, i can get rid of factory and just make $http.get('/user/me').then(..) call.
  • Benjamin Gruenbaum
    Benjamin Gruenbaum about 9 years
    @wdphd you can, the reason these calls are in a factory and not in the controller is because it makes for tidier code - not because it's magic :) Anything you can write in a factory you can also write inside the controller it just doesn't facilitate very good separation of concerns.
  • wdphd
    wdphd about 9 years
    @shaunhusain: You are returning the factObj within var factObj and its throwing me error. As said by Benjamin above, it returns null moving it out of factory obj
  • shaunhusain
    shaunhusain about 9 years
    Should work plnkr.co/edit/pVDWKb5HEtsTnnYo7ANH?p=preview your call to $http is still asynch so if you try to console.log the value from the factory immediately it will still be undefined until the response comes back, but since most of the time you're binding to the data in the view anyhow watchers take care of updating it when the $apply runs from the $http call. I also removed the annotations since I just do this with ngAnnotate during the build process.
  • wdphd
    wdphd about 9 years
    @shaunhusain: This is actually working! Is there a way to pass parameters to getUser using the above method?
  • shaunhusain
    shaunhusain about 9 years
    @wdphd you can still call the getUser from a particular controller instead of within the factory itself since there's still a reference to the getUser function on the factory. You'd probably want to add some more logic in the factory though to store the promise from the call to $http.get and just return that same promise every time getUser is called (or each time it's called with the same parameter, you can make a hash/object that stores all the promises "indexed" by the argument, same goes for the data itself returned from the promises).