1
\$\begingroup\$

I have written an AngularJS factory for caching resources fetched using ngResource. It works, but I'm thinking it can be improved.

My main question is: Would it be better to use prototype inheritance instead?
A side question is: Did I do the ngdoc right? The specifications for this haven't been documented very well and don't even seem to be always consistent in the AngularJS docs themselves.

'use strict';

/* global
  angular
*/

angular.module('dc.app.cachedResource', [
  'ngResource',
  'dc.formpost'
]).

/**
 * @ngdoc function
 * @module dc.app.cachedResource
 * @name cachedResource
 * @requires $http
 * @requires $resource
 * @requires formData
 *
 * @description
 * Returns a resource context.
 *
 * Create a cache context.
 *
 * @example
 * ```js
 *   factory('Component', function(cachedResource) {
 *     // There is only one global context.
 *     return cachedResource('/:appId/component', true)();
 *   }).
 *   factory('NewsItemContext', function(cachedResource) {
 *     // Multiple contexts may be created.
 *     return cachedResource('/:appId/component/:compId/newsfeed/niId', true);
 *   }).
 *   controller(function($scope, $stateParams Component, NewsItemContext) {
 *     $scope.component = Component.$get($stateParams);
 *     var NewsItem = NewsItemContext($scope.component);
 *     $scope.newsItems = NewsItem.$query();
 *   });
 * ```
 */
factory('cachedResource', function($http, $resource, formData) {

  return function(url, paramDefaults, isFormData) {

    var globalCache = {};

    return function(contextId) {

      if(!globalCache[contextId]) {
        globalCache[contextId] = [];
      }
      var cache = globalCache[contextId];

      var transformRequest = isFormData ? formData : (
        $http.defaults.transformRequest);
      var resource = $resource(url, paramDefaults, {
        create: {
          method: 'POST',
          transformRequest: transformRequest
        },
        update: {
          method: 'PUT',
          transformRequest: transformRequest
        }
      });

      /**
       * @ngdoc constructor
       * @name CachedResource
       *
       * @param {Object} params The parameters to set.
       * @param {boolean} doCache If true, append the instance to the
       *                          cache already.
       */
      function CachedResource(params, doCache) {
        angular.extend(this, params);
        if(doCache) {
          cache.push(this);
        }
      }

      /**
       * @ngdoc method
       * @name cachedResource#$query
       *
       * @description
       * Returns the cached resources. If ``forceRefresh`` is
       * ``true``, cache is refreshed with data from the backend.
       *
       * @param {Object} params The parameters to pass to ngResource.
       * @param {boolean} forceRefresh Enforce a request to be made.
       * @returns The array containing the cached resources.
       */
      CachedResource.$query = function(params, forceRefresh) {
        if(forceRefresh) {
          resource.prototype.$query.call(params, function(data) {
            cache.length = 0;
            data.forEach(function(data) {
              cache.push(new CachedResource(data));
            });
          });
        }
        return cache;
      };

      /**
       * @ngdoc method
       * @name cachedResource#$get
       *
       * @description
       * Retrieves a single entity.
       * First it tries to retrieve it from cache. If it doesn't exist
       * it asks the backend.
       *
       * @param {Object} params The parameters to pass to ngResource.
       * @param {boolean} forceRefresh Enforce a request to be made.
       * @returns {CachedResource} the matching entity.
       */
      CachedResource.$get = function(params, forceRefresh) {
        var result;
        cache.some(function(entity) {
          var equal = true;
          angular.forEach(params, function(value, key) {
            if(value !== entity[key]) {
              equal = false;
            }
          });
          if(equal) {
            result = entity;
            return true;
          }
        });
        if(result === undefined) {
          result = new CachedResource();
          resource.prototype.$get.call(params, function(data) {
            angular.extend(result, data);
            cache.push(result);
          });
        }
        return result;
      };

      /**
       * @ngdoc method
       * @name cachedResource#$create
       *
       * @description
       * Creates a resource on the backend and adds it to the cache.
       *
       * @param {Object} params The parameters to pass to ngResource.
       * @returns {CachedResource} ``this``
       */
      CachedResource.prototype.$create = function(params) {
        var self = this;
        resource.prototype.$create.call(this, params, function(data) {
          angular.extend(self, data);
          if(cache.indexOf(self) === -1) {
            cache.push(self);
          }
        });
        return this;
      };

      /**
       * @ngdoc method
       * @name cachedResource#$update
       *
       * @description
       * Synchronizes the resource with the backend.
       *
       * @param {Object} params The parameters to pass to ngResource.
       * @returns {CachedResource} ``this``
       */
      CachedResource.prototype.$update = function(params) {
        var self = this;
        resource.prototype.$update.call(this, params, function(data) {
          angular.extend(self, data);
        });
        return this;
      };

      /**
       * @ngdoc method
       * @name cachedResource#$delete
       *
       * @description
       * deletes itself from the backend and cache.
       *
       * @param {Object} params The parameters to pass to ngResource.
       */
      CachedResource.prototype.$delete = function(params) {
        var self = this;
        resource.prototype.$delete.call(this, params, function() {
          var i = cache.indexOf(self);
          if(i !== -1) {
            cache.splice(i, 1);
          }
        });
      };

      return CachedResource;
    };
  };
});

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.