17

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.

wdphd
  • 905
  • 6
  • 15
  • 25
  • The object you see in the console.log is the promise returned from the call to .get in your call to .getUser. The call to .then creates a new promise and when you return something from there it modifies the data resolved for any subsequent then's. – shaunhusain Jan 26 '15 at 08:15
  • check this answer from me from a different post: http://stackoverflow.com/a/38007172/1739949 – Barani r Jun 24 '16 at 06:50

3 Answers3

26

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.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • But if i'm using then() in controller, i can get rid of factory and just make $http.get('/user/me').then(..) call. – wdphd Jan 26 '15 at 08:18
  • 4
    @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. – Benjamin Gruenbaum Jan 26 '15 at 08:20
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.

shaunhusain
  • 19,630
  • 4
  • 38
  • 51
  • This would still fail - OP's issue is with the asynchronisity of the `get` not with where the data is stored. – Benjamin Gruenbaum Jan 26 '15 at 08:14
  • 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 http://plnkr.co/edit/cqyeXB5WgKZynQZdvEYP?p=preview – shaunhusain Jan 26 '15 at 08:15
  • 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](https://github.com/angular/angular/issues). The behavior I'm describing is well specified. – Benjamin Gruenbaum Jan 26 '15 at 08:18
  • @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 – wdphd Jan 26 '15 at 08:37
  • Should work http://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. – shaunhusain Jan 26 '15 at 18:15
  • @shaunhusain: This is actually working! Is there a way to pass parameters to getUser using the above method? – wdphd Jan 28 '15 at 08:21
  • @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). – shaunhusain Jan 28 '15 at 21:44
0

I have here a similar scenario.. I hope it helps somebody...

my factory ..

 angular.module('programList.services',[])
    .factory('PROGRAMS', function($http) {
     return {
       getprogramList: function() {
       return $http.get('/api/programs/list').then(function(result) {
       return result.data[0];
       });
      }
     };
    });

This is my controller ..

 angular.module('programList.services'])
 .controller('tviProgramsController',
    function($scope,$state,$stateParams,PROGRAMS){
        //this is the call to access the list as answered by Benjamin Above using then on success 
        PROGRAMS.getprogramList().then(function(list){
                  $scope.programlist = list;
              });
  });

;

This is on the router on server side...

  var express     = require('express'),
     programscontroller = require(path + 'programservercontroller'),
     router         = new express.Router();
     router.get('/api/programs/list', programscontroller.programlist);

This is the module for mysql call

var con   = require('../../database/dbcon'),
mysql = require('mysql');

module.exports.programlist = function(req, res){
 var data = req.params.data;
 var sql = "CALL `sp_programlist`";
  con.query(sql, function(err, result) {
      if(err){
          throw err;
      } else {
          res.send(JSON.stringify(result));
      }
     });
};
sireken
  • 29
  • 4