0

i have a resource like:

angular.module('mymodule')
    .factory('someResource', someResource);

function someResource($resource) {
  return $resource('/something', {}, {
    find  : {method: 'GET', isArray: true, transformResponse: convertFn},
    create: {method: 'POST', isArray: true, transformResponse: convertFn},
    update: {method: 'PUT', isArray: true, transformResponse: convertFn},
  });

  function convertFn(){
    //...
  }

}

is it possible to not to copypaste the transformResponse in every type of request? Define some default transformResponse?

possible solution just to modify the definition object by adding the property programmatically, but such solution looks hard to maintain.

angular.module('mymodule')
    .factory('someResource', someResource);

function someResource($resource) {
  var types = {
    find  : {method: 'GET', isArray: true},
    create: {method: 'POST', isArray: true},
    update: {method: 'PUT', isArray: true},
  }

  //add transform to each, es6
  Object.keys(types).forEach(k => types[k].transformResponse = convertFn)

  return $resource('/something', {}, types);

  function convertFn(){
    //...
  }

}

edit
thx to georgeawg for the idea
another way could be: write a wrapper function for defaults like:

angular.module('mymodule')
    .factory('someResource', someResource);

function someResource($resource) {
  var types = {
    find  : defaults({method: 'GET'}),
    create: defaults({method: 'POST', isArray: false}),
    update: defaults({method: 'PUT'}),
  }

  return $resource('/something', {}, types);

  function convertFn(){
    //...
  }

  function defaults(opts) {
    return Object.assign({
      isArray: false,
      transformResponse: convertFn
    }, opts)
  }

}

is there some cleaner solution for it?

Community
  • 1
  • 1
ya_dimon
  • 3,483
  • 3
  • 31
  • 42

3 Answers3

1

How about:

angular.module('mymodule')
    .factory('someResource', someResource);

function someResource($resource) {
  return $resource('/something', {}, {
    find  : action('GET'),
    create: action('POST'),
    update: action('PUT')
  });

  function action(method) {
      return { method: method,
               isArray: true,
               transformResponse: convertFn
              };
  }

  function convertFn(){
    //...
  }

}

Using a Response Interceptor

Since the $resource uses the $http service under the hood, a response interceptor can transform responses:

app.config(function($httpProvider) {
    $httpProvider.interceptors.push(function() {
      return {
       'request': function(config) {
           //
        },

        'response': function(response) {
          if (response.config.url.startsWith("/something") {
              response.data = convertFn(response.data);
          };
          return response;

          function convertFn(data) {
             //return new data
          }
        }
    });
});
georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • thx, looks easier, but there is a problem, if you want to modify one action-option, like `isArray: false` or add new option. The method should be like a wrapper for options, that sets defaults. – ya_dimon Nov 04 '16 at 14:42
  • Added section on response interceptors – georgeawg Nov 04 '16 at 19:01
0

Have you looked at angular $http interceptors? ngResource would honor a response interceptor function. Here's a post that details usage.

Community
  • 1
  • 1
Larry Turtis
  • 1,907
  • 13
  • 26
  • thx. the interceptors are global, and works for each request/resource then. That means i should write condition like `if url .. ` in the interceptor. Looks harder to maintain :/ – ya_dimon Nov 04 '16 at 14:51
0

It is possible to have a very generic base $resource and inherit it.

I found the answer on a SO post that i can't found anymore. If someone found it edit my post to add it.

Here is the code that I use :

angular.baseResourceServiceMaker = function(service){
    return ['$injector', '$resource', 'TypeService', '$http', '_', 'BackEndpoint', 'Utils',
            function($injector, $resource,TypeService, $http, _, BackEndpoint, Utils){
        this.restUrl = BackEndpoint+'/rest/';
        this.baseName = '';
        this.resource = null;
        // from angular-resource
        var toString= function() {
            var value = [];
            _.forEach(this, function(e) {
                value.push('' + e);
                });
            return '[' + value.join(', ') + ']';
          };
          var isObject = function isObject(value) {
              // http://jsperf.com/isobject4
              return value !== null && typeof value === 'object';
            };
        var isFile = function(obj) {
          return toString.call(obj) === '[object File]';
        }


        var isFormData = function(obj) {
          return toString.call(obj) === '[object FormData]';
        }


        var isBlob = function(obj) {
          return toString.call(obj) === '[object Blob]';
        }
        this.defaultToJson = function(d) {
              return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? angular.toJson(d) : d;
        };
        this.typeServiceProcessData = function(d){
            return TypeService.processData(d);
        };
        this.typeServiceProcessJsData = function(d){
            return TypeService.processJsData(d);
        };
        this.generateTransformRequestFn = function(mapKeyValues){
            return function(data){
                var object = {};
                _.forEach(_.keys(mapKeyValues), function(key){
                    Utils.setAttributeValue(object, key, Utils.getAttributeValue(data, mapKeyValues[key]));

                });
                return object;
            }
        };

        this.addedMethods = {};
        // use of resource will be internal, to handle transformation of data
        // and so on...
        this.getResource = function(){
            if(this.resource == null){
                var baseResourceUrl = this.restUrl + this.baseName + '/';
                var baseResourceMethods = {
                        'get':      {method:'GET', transformResponse:[$http.defaults.transformResponse[0], this.typeServiceProcessData],
                            url:baseResourceUrl+':id'},
                        'create':   {method:'POST', url:baseResourceUrl, transformRequest:[this.typeServiceProcessJsData, this.defaultToJson]},
                        'update' :  {method:'PUT', transformRequest:[this.typeServiceProcessJsData, this.defaultToJson]},
                        'search':   {method:'GET', isArray:true, transformResponse:[$http.defaults.transformResponse[0], this.typeServiceProcessData], 
                            url:baseResourceUrl+'search/(:search)/:offset/:limit/:order',
                            params: {offset:0, limit:50, order:"creationDate=asc"}
                        },
                        'custom_search':    {method:'GET', isArray:true, transformResponse:[$http.defaults.transformResponse[0], this.typeServiceProcessData], 
                            url:baseResourceUrl+':prefix/search/(:search)/:offset/:limit/:order',
                            params: {search:'pk=gt=0',offset:0, limit:50, order:"creationDate=asc"}
                        },
                        'list':     {method:'GET', isArray:true, transformResponse:[$http.defaults.transformResponse[0], this.typeServiceProcessData], 
                            url:baseResourceUrl+'search/(pk=gt=0)/0/50/creationDate=asc'
                        },

                        'delete':   {method:'DELETE'} 
                    };
                _.forEach(_.keys(this.addedMethods), function(key){
                    baseResourceMethods[key] = this.addedMethods[key];
                }, this)
                this.resource = $resource(baseResourceUrl+':id',
                    {id:'@pk'}, baseResourceMethods
                    );
            }
            return this.resource;
        };
        this.get = function(id){
            this.getResource().get({id:id});
        };
        this.create = function(data){
            this.getResource().create(data);
        };
        this.update = function(data){
            this.getResource().update(data);
        };
        this.search = function(searchQuery){
            this.getResource().search({search:searchQuery});
        };
        this.searchPaginate = function(searchQuery, offset, limit){
            this.getResource().search({search:searchQuery, offset:offset, limit:limit});
        };
        this['delete'] = function(id){
            this.getResource()['delete']({id:id});
        };
           // Finishes the other injections
        $injector.invoke(service, this);

    }];
};

Some comments about this code :

  • The functions isFile/isObject,... are c/c from angular.js because I keep the defaultTransformResponse from angularJS, this function use internal function that are not in my scope so I had to cc it.
  • I define default methods create/update/...
  • I have a typeService where i declare all my type and fields, so i can automatically convert type : for instance my server's date are always timestamps, so i convret it automatically to Javascript Date in transformResponse and the opposite in transformRequest.
  • addedMethods is used to add others method.
  • restUrl is the entry point of all rest services, baseName must be set by the implementation, it's the entry point for the resource. BackEndPoint is the constant that define the contextPath of my application.

Example of usage :

.service('ArticleService',angular.baseResourceServiceMaker(['$http', function($http){
      this.baseName = 'article';
      var baseResourceUrl = this.restUrl + this.baseName + '/';
      this.addedMethods.root ={
              method:'GET', isArray:true, 
              transformResponse:[$http.defaults.transformResponse[0], this.typeServiceProcessData], 
              url:baseResourceUrl+'root'
      };
}]))
Walfrat
  • 5,363
  • 1
  • 16
  • 35