5

I'm using a service to make user data available to various controllers in my Angular app. I'm stuck trying to figure out how to use the $http service to update a variable local to the service (in my case "this.users"). I've tried with and without promises. The server is responding correctly.

I've read several excellent articles for how to use $http within a service to update the scope of a controller. The best being this one: http://sravi-kiran.blogspot.com/2013/03/MovingAjaxCallsToACustomServiceInAngularJS.html. That does not help me though because it negates the benefits of using a service. Mainly, modifying the scope in one controller does not modify throughout the rest of the app.

Here is what I have thus far.

app.service('UserService', ['$http', function($http) {
    this.users = [];

    this.load = function() {
        var promise = $http.get('users.json')
            .success(function(data){
                // this.users is undefined here
                console.log(this.users);
            }
    };

    promise.then(function() {
        // this.users is undefined here
        console.log('this.users');
    });
}]);

Any help is greatly appreciated. Thank you.

Omri Aharon
  • 16,959
  • 5
  • 40
  • 58
guyja
  • 857
  • 1
  • 10
  • 19

4 Answers4

3

Try using

var users = [];

rather than

this.users = [];

and see what

console.log(users);

outputs in each of those cases.

sfletche
  • 47,248
  • 30
  • 103
  • 119
  • 1
    You are correct. However, how then would I bind a controller $scope variable to users? – guyja Apr 30 '14 at 20:56
  • 1
    you wouldn't, in your controller, you would have to make an explicit call to get that from the user service – SoluableNonagon Apr 30 '14 at 20:58
  • What about 2-way binding. Lets say I have $scope.users in my controller that I update with a getter method from the service. I want to be able to update the service users value when $scope.users changes. I realize I could implement a setter method on the service but then I have to handle binding manually everywhere. That seems to reduce the value of Angular's built-in binding. Am I going about this the entirely wrong way? – guyja Apr 30 '14 at 21:06
  • 1
    you create a method in the service that returns the var users this.get = function(){ return users } then in your controller you set $scope.users = usersService.get(); and then your two way data binding will work – btm1 May 06 '14 at 17:34
  • This is the correct way of using `observer pattern` in `angular controller-service` interaction. – faizanjehangir May 12 '16 at 15:02
3

Your service is oddly defined, but if you have a return in it you can access it from any controller:

app.service('UserService', ['$http', function($http) {
    var users = [];
    this.load = function() {
        var promise = $http.get('users.json')
            .success(function(data){
                // this.users is undefined here
                console.log(users);
                users = data.data;
            }
    };
    return {
        getUsers: function(){
            return users;
        }
    }  
}]);

so in your controller, you can use:

var myUsers = UserService.getUsers();

UPDATE to use a service correctly here, your service should return a promise and the promise should be accessed in the controller: Here's an example from another answer I gave

// your service should return a promise
app.service('PickerService', [$http', function($http) {
    return {
        getFiles: function(){ 
            return $http.get('files.json'); // this returns a promise, the promise is not executed here
        }
    }
}]);

then in your controller do this:

PickerService.getFiles().then(function(returnValues){ // the promise is executed here as the return values are here
    $scope.myDirectiveData = returnValues.data;
});
RChanaud
  • 194
  • 2
  • 12
SoluableNonagon
  • 11,541
  • 11
  • 53
  • 98
  • I'm new to Angular. Can you let me know what you mean by oddly defined? – guyja Apr 30 '14 at 21:09
  • It's not wrong, but it doesn't return anything, normally when you have a service, it will do something, but as it behaves like a singleton, it is used to store data and return it. Your service runs when it is instantiated, but then does nothing with that data. – SoluableNonagon Apr 30 '14 at 21:15
  • I thought that you only return something if you're using the factory method to generate a service. app.factory('ServiceName', function() { return {...}}. – guyja Apr 30 '14 at 21:18
  • See my updated code. In your example, you are doing an $http call to get data, and you want to access that data in your controller. So that means you need to call it from a controller. If you want to save the data you get back to the service, define a setData function in your service and call it after the .then() once you get data, then it will be accessible via any controller. The service will not broadcast to all your controllers. – SoluableNonagon Apr 30 '14 at 21:21
  • factory and service are VERY similar in how you can use them, but here's a simple article: http://stackoverflow.com/questions/14324451/angular-service-vs-angular-factory to help out – SoluableNonagon Apr 30 '14 at 21:25
  • If I understand you correctly then I would create a service with a local users variable and two methods (load and set). The load method would use $http.get to load data from the server. The set method would update the users variable. The controller would then call load. Within "then(...)", the controller would BOTH update the $scope AND call the service's set method to update the service's local users value. That leads to one more question. How do I automatically update the service's users value whenever $scope.users changes in the controller? – guyja Apr 30 '14 at 21:35
0

this does not have scope anymore where you are trying to use it do this instead:

app.service('UserService', [$http', function($http) {
var users = [];

this.load = function() {
    var promise = $http.get('users.json')
        .success(function(data){
            console.log(users);
        }
};

    promise.then(function() {
    console.log(users);
    });
}]);

all local variables to a service should just be vars if you assign them to this as a property than they will be included every time the service is injected into a controller which is bad practice.

btm1
  • 3,866
  • 2
  • 23
  • 26
0

I think what your asking for is a solution along the lines of defining your service like this:

angular.module('app')
  .service('User', function($http, $q) {
    var users = null;
    var deferred = $q.defer()

    return {
      getUsers: function() {
        if(users) {
          deferred.resolve(users);
        } else {

          $http.get('users.json');
            .success(function(result) {
              deferred.resolve(result);
            })
            .error(function(error) {
              deferred.reject(error);
            });
        }
        return deferred.promise;
      }
    };
  });

Then in one Each controller you would have to do this:

angular.module('app')
  .controller('ACtrl', function($scope, User) {
    User.getUsers().then(function(users) {
      // Same object that's in BCtrl
      $scope.users = users;
    });
  });

angular.module('app')
  .controller('BCtrl', function($scope, User) {
    User.getUsers().then(function(users) {
      // Same object that's in ACtrl
      $scope.users = users;
    });
  });

NOTE: Because the deferred.promise the same promise passed to all controllers, executing deferred.resolve(users) in the future will cause all then success callbacks in each of your controllers to be called essentially overwriting the old users list.

All operations on the list will be noticed in all controllers because the users array is a shared object at that point. This will only handle updates to the user list/each individual user on the client side of your application. If you want to persist changes to the server, you're going to have to add other $http methods to your service to handle CRUD operations on a user. This can generally be tricky and I highly advise that you check out ngResource, which takes care of basic RESTful operations

Mike Quinlan
  • 2,873
  • 17
  • 25