nathasm
nathasm

Reputation: 2574

Angular Best Practice: Data at start-up in a service

I want to create an angular service that grabs data at "start-up" and builds up an array that will not change for the lifetime of the application. I don't want to return the promise of the http request. Caching the result would still incurs the cost of the 'someExpensiveFunctionCall'. What's the best way to handle this situation that can guarantee that the service will have the result populated and can be used in a controller and/or another service.

angular.module('app')
.service('MyService', function($http, defaults) {
  var result = [];
  $http.get('/some/url').then(function(data) {
    if(angular.isDefined(data)) {
      _.forEach(data, function(item) {
        result.push(someExpensiveFunctionCall(item.value));
      });
    } else {
      result = angular.copy(defaults.defaultResult);
    }
  });
  return {
    result: result
  };
})
.controller(function(MyService) {
  var someData = MyService.result;
})
.service(function(MyService) {
  var someData = MyService.result;
});

Upvotes: 1

Views: 113

Answers (1)

calebboyd
calebboyd

Reputation: 5753

I don't want to return the promise of the http request.

It seems like you want to do something that is asynchronous; synchronously. This can't work (reliably) without some sort of callback, promise, sync XHR, or other mechanism.

Knowing that we can evaluate some options. (in order of most practical)

  1. Use your routers resolve property. This will complete some promise returning operation before instantiating your controller. It also (in ui-router and ngRoute) means the result of the promise will be injected into the controller under the specified key name.

  2. Use the promise.

(aka #1 manually)

.factory('MyService',function($http,defaults){
    var ret = {result: []}; 
    ret.promise = $http.get('url').then(function(data){
        if(angular.isDefined(data)){
            _.forEach(data,function(item){
                ret.result.push(someExpensiveFunctionCall(item.value));
            });
        }else{
            _.forEach(angular.copy(defaults.defaultResult),function(val){
                ret.result.push(val);    
            });
        }
        return ret;
    });        
    return ret;
})
.controller('MyCtrl',function(MyService){
    MyService.promise.then(myCtrl);
    function myCtrl(){
        var result = MyService.result
    }
})
  1. Lazy Evaluation. --> this is a little slow in angular due to dirty checking. But essentially means that you don't immediately manipulate/read result inside the body of a controller or service. But instead, use the view bindings, filters and interactions to call scope functions that act on the data. Since the resolution of a promise will invoke a digest cycle. Your view will render when the data arrives.

There was a bug in your code if the data happens to be undefined. You are de-referencing result by setting it to a copy of the defaults. So all references to MyService.result will always be an empty array and never the default value. In order to maintain the reference I'd loop over the defaults and add them to result with result.push.

Hope this helps!

Upvotes: 1

Related Questions