How can I extend the constructor of an AngularJS resource ($resource)? How can I extend the constructor of an AngularJS resource ($resource)? angularjs angularjs

How can I extend the constructor of an AngularJS resource ($resource)?


$resource is a simple implementation, and lacks in things like this.

User.prototype.constructor won't do anything; angular doesn't try to act like it's object oriented, unlike other libraries. It's just javascript.

..But luckily, you have promises and javascript :-). Here's a way you could do it:

function wrapPreferences(user) {  user.preferences = _.map(user.preferences, function(p) {    return new Preference(p);  });  return user;}var get = User.get;User.get = function() {  return get.apply(User, arguments).$then(wrapPreferences);};var $get = User.prototype.$get;User.prototype.$get = function() {  return $get.apply(this, arguments).$then(wrapPreferences);};

You could abstract this into a method which decorates any of a resource's methods: It takes an object, an array of method names, and a decorator function.

function decorateResource(Resource, methodNames, decorator) {  _.forEach(methodNames, function(methodName) {    var method = Resource[methodName];    Resource[methodName] = function() {      return method.apply(Resource, arguments).$then(decorator);    };    var $method = Resource.prototype[methodName];    Resource.prototype[methodName] = function() {      return $method.apply(this, arguments).$then(decorator);    };  });}decorateResource(User, ['get', 'query'], wrapPreferences);


You can do this by overriding the built-in resource actions to transform the request and response (See transformRequest and transformResponse in the docs.):

var m = angular.module('my-app.resources');m.factory('User', [          '$resource',  function($resource) {    function transformUserFromServer(user) {      // Pass Preference directly to map since, in your example, it takes a JSON preference as an argument      user.preferences = _.map(user.preferences, Preference);      return user;    }    function transformUserForServer(user) {      // Make a copy so that you don't make your existing object invalid      // E.g., changes here may invalidate your model for its form,       //  resulting in flashes of error messages while the request is       //  running and before you transfer to a new page      var copy = angular.copy(user);      copy.preferences = _.map(user.preferences, function(pref) {        // This may be unnecessary in your case, if your Preference model is acceptable in JSON format for your server        return {          id: pref.id,          title: pref.title,          value: pref.value        };      });      return copy;    }    function transformUsersFromServer(users) {      return _.map(users, transformUserFromServer);    }    return $resource('/user/:userId', {        userId: '@id'      }, {        get: {          method: 'GET',          transformRequest: [            angular.fromJson,            transformUserFromServer          ]        },        query: {          method: 'GET',          isArray: true,          transformRequest: [            angular.fromJson,            transformUsersFromServer          ]        },        save: {          method: 'POST',          // This may be unnecessary in your case, if your Preference model is acceptable in JSON format for your server          transformRequest: [            transformUserForServer,            angular.toJson          ],          // But you'll probably still want to transform the response          transformResponse: [            angular.fromJson,            transformUserFromServer          ]        },        // update is not a built-in $resource method, but we use it so that our URLs are more RESTful        update: {          method: 'PUT',          // Same comments above apply in the update case.          transformRequest: [            transformUserForServer,            angular.toJson          ],          transformResponse: [            angular.fromJson,            transformUserFromServer          ]        }      }    );  };]);


I was looking for a solution to the same problem as yours. I came up with the following approach.
This example is based on Offers instead of Users, as domain entity. Also, please note here's a trimmed down version of the whole thing, which in my case spans over some files:

Domain entity custom class:

function Offer(resource) {    // Class constructor function    // ...}angular.extend(Offer.prototype, {    // ...    _init: function (resource) {        this._initAsEmpty();        if (typeof resource == 'undefined') {            // no resource passed, leave empty        }        else {            // resource passed, copy offer from that            this.copyFromResource(resource);        }    },    copyFromResource: function (resource) {        angular.extend(this, resource);        // possibly some more logic to copy deep references    },    // ...});

Classic angular custom resource:

var offerResource = $resource(/* .. */);

Custom repository, passed to controller by a service factory:

function OfferRepository() {      // ...}angular.extend(OfferRepository.prototype, {    // ...    getById: function (offerId, success, error) {        var asyncResource = offerResource.get({            offerId: offerId        }, function (resource) {            asyncOffer.copyFromResource(resource);            (success || angular.noop)(asyncOffer);        }, function (response) {            (error || angular.noop)(response);        });        var asyncOffer = new offerModels.Offer(asyncResource);        return asyncOffer;    },    // ...});

Most noticeable parts are:

  • the custom entity class, that is able to construct/fill itself starting from a resource instance (possibly with deep copy capabilities, e.g. Positions in an Offer)
  • the custom repository class, which wraps the resource. That does not returns the classic async resource answer, but instead returns an custom entity instance, and later on it fills that with the resource just loaded.