woutr_be
woutr_be

Reputation: 9722

Angular.js service/factory with reusable data

I'm trying to build a service or factory that allows me to load date through some API calls. Most of this data will need to be re-used, so essentially I only want to make one API call, the next time I need that data, it should just return it.

Now whenever I make an API call, and before it's finished I make the same call, I want the second call to wait until the first one is completed.

Essentially when I do this:

dataService.getMenu() // Make API call
dataService.getMenu() // Wait for the first API call to be completed and return that data

// Somewhere else
dataService.getMenu() // Return data as API call was already made

My factory looks like this:

(function() {
    var app = angular.module('dataService', []);
    app.factory('dataService', ['$http', '$q', function($http, $q) {
        var links = [],
            jobs = [];

        return {
            getMenu: function() {
                var deferred = $q.defer();

                console.log(links);

                if(links.length > 0) {
                    deferred.resolve(links);
                } else {
                    $http.get('../server/api.php?ajax=true&action=getCats').success(function(data) {
                        links = data;

                        deferred.resolve(data);
                    })
                }

                return deferred.promise;
            }
        }
    }])
})();

Upvotes: 1

Views: 994

Answers (3)

Nikhil Batra
Nikhil Batra

Reputation: 3148

I know I am bit late to answer this question, but I faced the same problem and came up with a solution after lot of research.

The approach to achieve the above requirement is to use queuing of calls.

The following are the steps for the same:

  1. For each call, create a promise and add the promise to the queue. Return defer.promise for each call.
  2. For the first item in the queue, call a function which will bring your api and in the response of the API, set a parameter say IsDataPresent = true(initially it was false).
  3. Resolve the promise of the first call and set received data in a local variable. Execute the function for next call in the queue, but first check for IsDataPresent=== true, if true then resolve the next call's promise with the data of local variable.

See the code below for the same:

app.factory('dataService', ['$http', '$q', function($http, $q) {
    var links = '',
        jobs = [],
        isDataAlreadyPresent = false;

    var getMenuCall = function() { 
        var call = jobs[0]; // Retrieve first promise from the queue
        if (isDataAlreadyPresent) { // This will be false for first call and true for all other
            call.defer.resolve(links);
        } else {
            $http.get('../server/api.php?ajax=true&action=getCats').success(function(data) { //Get API data
                isDataAlreadyPresent = true; //Set parameter to true
                links = data; // Set local variable to received data
                call.defer.resolve.resolve(data); // Resolve the first promise
                jobs.shift(); // Remove first item from the queue
                if (jobs.length > 0) {
                    getMenuCall(); // Execute the function for next call's promise in the queue. This time isDataAlreadyPresent== true will be true so it's promise will be resolved by the links data thus avoiding extra call. 
                }
            });
        }

        return deferredMenu.promise;
    };

    return {
        getMenu: function() {
            var defer = $q.defer(); // Create promise for the call
            jobs.push({
                defer: defer // Push each call's promise to the queue
            });
            if (jobs.length === 1) { // For the first call make call above function which will make API call
                getMenuCall();
            }
            return defer.promise; // Defer promise for the time being
        }
    }
}]);

Upvotes: 0

ryeballar
ryeballar

Reputation: 30108

You can make use of the cache config in the $http service to do this for you. As stated in the $http caching documentation:

To enable caching, set the request configuration cache property to true (to use default cache) or to a custom cache object (built with $cacheFactory). When the cache is enabled, $http stores the response from the server in the specified cache. The next time the same request is made, the response is served from the cache without sending a request to the server

Note that even if the response is served from cache, delivery of the data is asynchronous in the same way that real requests are.

If there are multiple GET requests for the same URL that should be cached using the same cache, but the cache is not populated yet, only one request to the server will be made and the remaining requests will be fulfilled using the response from the first request.

The statement above fits the requirements stated in your question. Furthermore, I have omitted the $q service since $http methods already provide promises that you need, you simply need to have the data object in your response using the then() $q service method.

(function() {
    var app = angular.module('dataService', []);
    app.factory('dataService', ['$http', function($http) {
        return {
            getMenu: function() {
               return $http.get('../server/api.php?ajax=true&action=getCats', {cache: true})
                  .then(function(response) {
                    return response.data;
                  });
            }
        };
    }])
})();

Upvotes: 0

Chandermani
Chandermani

Reputation: 42669

Just move the declaration for defer outside the getMenu function, into factory

 app.factory('dataService', ['$http', '$q', function($http, $q) {
        var links = [],
            jobs = [],
            deferredMenu = $q.defer();

Now use deferredMenu promise in side your getMenu call.

getMenu: function() {
                if(links.length > 0) {
                    deferredMenu.resolve(links);
                } else {
                    $http.get('../server/api.php?ajax=true&action=getCats').success(function(data) {
                        links = data;

                        deferredMenu.resolve(data);
                    })
                }

                return deferredMenu.promise;
}

Upvotes: 2

Related Questions