Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS service storing $http results to prevent requerying --- is there a better way to do this?

The Setting: I want to have a service that multiple controllers can query for data pulled using $http. The initial solution was to use promises as suggested here.

The Problem: Each time a controller queries the service, the service then returns an $http promise, resulting in multiple queries that just pulls the same data from a remote server, over and over again.

A Solution: The service function returns either data or a promise like below. And it is up to the controller to check and act accordingly.

app.factory('myService', function($http) {
    var items = [];
    var myService = {
        getItems: function() {
            // if items has content, return items; otherwise, return promise.
            if (items.length > 0) {
                return items;
            } else {     
                var promise = $http.get('test.json').then(function (response) {
                    // fill up items with result, so next query just returns items.
                    for(var i=0;i<response.data.length;i++){
                        items.push(response.data[i]);
                    }
                    return items;
                });
                // Return the promise to the controller
                return promise;
            }
     };
     return myService;
});

So when a controller needs that data, the controller just does something like this:

app.controller('MainCtrl', function( myService,$scope) {
    var promiseOrData = myService.async();
    // Check whether result is a promise or data.
    if ( typeof promiseOrData.then === 'function'){
        // It's a promise.  Use then().
        promiseOrData.then( function(data ){
            $scope.data = data;
        });
    } else {
        // It's data.
        $scope.data = data;
    }
});

So the question is: Is there a better way of doing this? With many controllers, this method would have a lot of duplicate code. Ideally, the controllers will just query the service for data directly.

Thanks!

like image 871
RVC Avatar asked Aug 09 '13 00:08

RVC


2 Answers

$http returns a promise, we can use that instead of creating a new one with $q. Once the promise is resolved, we can keep returning it.

.factory('myService', ['$http','$q', function($http, $q) {
    var items = [];
    var last_request_failed = true;
    var promise = undefined;
    return {
        getItems: function() {
            if(!promise || last_request_failed) {
                promise = $http.get('test.json').then(
                function(response) {
                    last_request_failed = false;
                    items = response.data;
                    return items;
                },function(response) {  // error
                    last_request_failed = true;
                    return $q.reject(response);
                });
            }
            return promise;
        },
    };
}])

In your controller:

myService.getItems().then( 
    function(data) { $scope.data = data; }
);
like image 118
Mark Rajcok Avatar answered Oct 05 '22 09:10

Mark Rajcok


Create your own promise that resolves to either the cached data or the fetched data.

app.factory('myService', function($http, $q) {
    var items = [];
    var myService = {
        getItems: function() {
            var deferred =  $q.defer();

            if (items.length > 0) {
                 //resolve the promise with the cached items
                deferred.resolve(items);
            } else {     
                $http.get('test.json').then(function (response) {
                    // fill up items with result, so next query just returns items.
                    for(var i=0;i<response.data.length;i++){
                        items.push(response.data[i]);
                    }
                    //resolve the promise with the items retrieved
                    deferred.resolve(items);
                },function(response){
                   //something went wrong reject the promise with a message
                   deferred.reject("Could not retrieve data!"); 
                });


            }
         // Return the promise to the controller
         return deferred.promise;
     };
     return myService;
});

Then consume the promise in your controller.

app.controller('MainCtrl', function( myService,$scope) {
    var promiseOrData = myService.getItems();

        promiseOrData.then( function(data){
            $scope.data = data;
        },
        function(data){
            // should log "Could not retrieve data!"
            console.log(data)
        });

});
like image 38
Jonathan Palumbo Avatar answered Oct 05 '22 08:10

Jonathan Palumbo