0

I realise this may just a regular javascript/asynchronous programming query, but AngularJS is where i'd most like to see a reduction in boilerplate for common tasks.

I commonly have these types of patterns in AngularJS controller, where the actual loading and storing of data is delegated to a Service.

$scope.user = null; 

$scope.init = function(){
    // load user
    profileService.loadUser(function(user){
        $scope.apply(function(){
            $scope.user = user;
        });
    });

};

It's quite a verbose 'getter'. Is there a better pattern to get data into the scope from a service?

Rob Shepherd
  • 866
  • 1
  • 9
  • 25
  • As far as I know, ajax requests will automatically trigger a digest, so doing `$scope.user = user;` should be enough no? – plalx Apr 01 '14 at 23:06
  • From what I have read in other answers on similar topics, the best route is to have your service/factory return a promise with the http request setting cache to `true` to reduce replicated requests. This [SO answer](http://stackoverflow.com/questions/12505760/angularjs-processing-http-response-in-service) has a nice example – caffeinated.tech Apr 01 '14 at 23:08
  • Notice that I don't divulge what is going in the Service. But sure, if the service calls the callback as part of a $q.then() it will be digested yes. But nevertheless, I often have caching that uses localStorage to dump common objects. These aren't loaded with an implicit digest. in which case I have to push the current scope into the service in order for it to $apply on the controllers behalf. no better – Rob Shepherd Apr 01 '14 at 23:11
  • @RobShepherd Could you queue a digest from the service instead, just like using `$.get` would? – plalx Apr 01 '14 at 23:12
  • Hi @plalx, would I need to pass the $scope to the Service? I.e. myService.doSomething(arg1, arg2, $scope) this seems a bit wrong to have to push this down a layer. – Rob Shepherd Apr 01 '14 at 23:25
  • @RobShepherd I think that they just queue a digest on `$rootScope`, so you can just do the same by asking for `$rootScope` in your service I believe. – plalx Apr 01 '14 at 23:33
  • https://github.com/angular/angular.js/blob/master/src/ng/http.js#L173 check out line 986 this is the $http service source code showing how they check the phase and call apply on rootScope to trigger a digest. If you're writing services and such that don't use $http or $resource or other angular services then it seems fine to just add this. if (!$rootScope.$$phase) $rootScope.$apply(); That said ordinarily I've just re-used data within a service in controllers and haven't needed to manually call apply in most cases. – shaunhusain Apr 02 '14 at 03:39
  • Thanks @shaunhusain, I've seen this before, but I don't really like how one needs to proliferate lower levels of code with cross-cutting concerns, like $scope and controller concerns inside lower levels. This means that to unit test services in isolation one needs to mock-out all of the stuff that it shouldn't have to care about etc. It feels a bit wrong to be hacking the view-model to work in service layers. That said - I'm currently experimenting with promise objects, as per the other suggestions, with thanks. – Rob Shepherd Apr 02 '14 at 09:35

1 Answers1

0

In your service, if you are doing an $http.get (or any other verb), it returns a Future Object. A future object is an object that is currently a promise, but will eventually be an object.

app.factory('profileService', function(){    

  return {
    loadUser: function(){
      return $http.get('http://www.example.com'); 
    }
  };        
});

This will return a future object. So in your code, you could do the following:

$scope.init = function(){
    $scope.user = profileService.loadUser();
    //no $scope.$apply necessary, as you are still inside a digest cycle. 
};

In the short term $scope.user will be a future object. But it will eventually resolve into an object.

To read more about this, check out the following LINK and look for Future Objects.

frosty
  • 21,036
  • 7
  • 52
  • 74