Angular.js: ngResource does not apply request interceptor

Created on 26 Nov 2013  Â·  49Comments  Â·  Source: angular/angular.js

ngResource removed transformRequest support but does not apply interceptor.request provided in actions. Shouldn't it?

ngResource low feature

Most helpful comment

It will be in 1.6.x (not sure about 1.5.x). You can track #13273 as well.

All 49 comments

the documentation of http://docs.angularjs.org/api/ngResource.$resource specifies transformRequest but it has no effect

Hi,
could you create a plunker for this? You can start with an example from the docs...

Is there any plunker example I can start with? I can't see any plunker in the documentation of $resource to start with...

Sorry, you have to start your own... But you can start with another example, e.g. for <input>.

No response from OP, so I'm going to close this one out.

Why? It's still a valid concern/bug.

If you provide a plunker demonstrating the issue, then I will reopen.

here you go http://plnkr.co/edit/WAd11CG7GvBWDSXkLKRh
once you look at the code (ng-resource), you will notice the flaw pretty fast

The code works as documented, so this isn't a bug.

interceptor - {Object=} - The interceptor object has two optional methods - response and 
responseError. Both response and responseError interceptors get called with http response 
object. See $http interceptors.

I'll open it back up as a feature request.

Any reason ngResource doesn't make use of request interceptors on the action.interceptor object?

As a feature request, this certainly gets my vote

+1 for the feature request

+1 for the request

proper interceptors can't really be supported without a major refactoring of $http, because interceptors for $http are setup during configuration, not per-request.

If it were going to be possible, would the $resource request interceptor run before, or after, the pre-configured interceptors? Neither would work for everybody. Providing an option to transform the request could be doable, but it would look very different from $http interceptors, so it might be problematic to make them look like they were one in the same.

Looking at the history, I don't see where transformRequest was ever supported in resource before, though, so I'm not sure if we ever had a good reason to remove it, if it was ever there to begin with.

:+1: This would be great feature added. I need this to convert query params from camel case to snake case, per request. I can't do it with transformRequest unfortunately.

@jtheoof, why can't you do it with transformRequest ?

Because transformRequest passes the payload of the request, not the whole config object.

GET /somepath/objects/aa0094f040120f708144e5f09d51978a?id_type=deviceid HTTP/1.1
Host: localhost
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
X-CSRF-TOKEN: blabla
...

In the ObjectsResource:

get: {
    method: 'GET',
    url: url + '/:id',
    transformRequest: function(request) {
        console.log(arguments);
        return request;
    },
}

Prints out: [undefined, function, undefined, callee: (...), caller: (...)].

The query params are gone, no way to do anything with it.

How are you setting the query params ?

In the service. Our backend only supports snake case, but our jshint rules are not happy about it. So I would like to create a transformer but ngResource does not allow to transform the query params in transformRequest.

In objectService:

        /**
         * Fetches an object based on id and and id type.
         * @param {String} id The id of the object to get.
         * @param {String} idType The type corresponding to the id. Could be 'objectid' by default
         * or 'deviceid'.
         * @return {Resource} An angular $resource instance.
         */
        this.get = function(id, idType) {
            var options = {
                id: id
            };

            if (_.isString(idType)) {
                options.idType = idType;
            }

            // Converting `idType` to `id_type`. This should be done in transformRequest.
            var resource = ObjectsResource.get(utilsService.camelCaseToSnakeCase(options));

            return resource;
        };

@jtheoof, I see what you mean. (Although, it sounds much simpler to fix the jshint rules, than adding an interceptor.)

It is probably not the case, but if the query params where fixed, you could do:

var ObjectsSource = new $resource('/some/url?id=:id&id_type=:idType', ...);

Good point, but they are not fixed... In my opinion, transformRequest should allow to transform the whole request, not only the payload. Or the function should be named transformRequestPayload. :wink:

FYI, it's undocumented but each _action_ config can add the paramSerializer which you can use to transform query parameters, eg

{
    get: {
        method: 'GET',
        paramSerializer: function(params) {
            // do stuff to params
            return $httpParamSerializer(params);
        }
    }
}

I'm using the paramSerializer for now until the feature is there. Seems to be working fine. Any reason why this is undocumented? I'm assuming this is presumably a private function to angular?

The paramSerializer is not private. It's part of $http's configuration (documented here and the available built-in serializers here and there).

The key-point is that the properties defined in the declaration object of custom $resource actions (with few exceptions, such as interceptor) are passed to $http(config).
This is admittedly underdocumented (_"The declaration should be created in the format of $http.config"_ being a small step into the right direction :smiley:).

Gotcha, that makes sense to me. Thanks for the explanation.

hmmm it doesn't work for me with angular $resource (I am using angular 1.4)
I also saw in the angular code that the paramSerializer will get passed to the http config, but the params in my custom function are always undefined?

Any help would be much appreciated!!

code snippet:

// RESOURCE
angular.module('app').factory('User', function ($resource, $httpParamSerializer) {
  var serializer = function (params) {
    console.log(params); // => is always undefined
    if(params && params.encodedEmail) {
      params.encodedEmail = decodeURIComponent(params.encodedEmail);
    }
    return $httpParamSerializer(params);
  };

  return $resource('.../user/:encodedEmail', {encodedEmail: '@encodedEmail'}, {
    get: {paramSerializer: serializer}
  });
});

// CALL in controller
User.get({encodedEmail: '2312'});

Here's the implementation that I used:

.factory('BVReviewFactory', [ '$q', '$http', '$resource', '$httpParamSerializer', 'BV_CONFIG', function($q, $http, $resource, $httpParamSerializer, bvConfig) {
    return $resource( bvConfig.apiUrl + '/data/:method', {
        apiVersion: bvConfig.apiVersion,
        passKey: bvConfig.apiKey
    }, {
        'submit': { 
            method: 'POST',
            isArray: false,
            withCredentials: false,
            paramSerializer: function(params) { // TODO: This should not be necessary
                if(angular.isUndefined(params.userNickname) || angular.isUndefined(params['AdditionalField_viewName'])) {
                    delete params.userNickname;
                    delete params['AdditionalField_viewName'];

                    return $httpParamSerializer(params);
                }

                params.userNickname = params.userNickname + bvConfig.nicknameSeparator + params['AdditionalField_viewName'];
                delete params['AdditionalField_viewName'];

                return $httpParamSerializer(params);
            },
            params: {
                method: 'submitreview.json',
                userEmail: '@userEmail',
                userId: '@userId',
                action: '@action',
                rating: '@rating',
                title: '@title',
                reviewText: '@reviewText',
                productId: '@productId',
                userNickname: '@userNickname',
                'AdditionalField_viewName': '@viewName'
            }
        },
        'retrieve': { 
            method: 'JSONP',
            isArray: false,
            interceptor: {
                response: function(response) {

                }
            },
            params: {
                callback: 'JSON_CALLBACK',
                method: 'reviews.json',
                include: 'Products,Categories,Authors,Comments', // TODO: This list could be slimmed down
                limit: '@limit',
                offset: '@offset',
                sort: '@sort',
                stats: 'Reviews' // Used to get rating distribution
            }
        }
    });
}]);

Although, I ended up not having to transform the request anyways.

What happens when you log the arguments? maybe params is not the first argument

@gitnik arguments is also undefined..

console.log(arguments); // => [undefined]

@lpsBetty the param serialiser is for query parameters, not those interpolated into the path.

@philBrown ah thanks! Does anyone know how I can change params before they get interpolated into the path?

Ok i found an issue for that: https://github.com/angular/angular.js/issues/1388

+1 for the feature request..

Not sure if it's exactly my case though.

I have a global $http interceptor that does some data transformations, and then I have a few particular $ngResources that need particular transformations, what I'd like to be able to do is to specify an array of interceptors in the $ngResource as its done in the $http.defaults service.

module.factory('globalInterceptor', function($q) {
  return {
    'request': function(config) {
      return config;
    }
  }
});

module.factory('localInterceptor', function(){
  ...
});

module.config(function($httpProvider){
  $httpProvider.interceptors.push('globalInterceptor');
});

module.service('myService', function($resource){
  return $resource('/something', null, {
    someAction: {
      method: 'POST',
      interceptors: ['globalInterceptor', 'localInterceptor']
    }
  });
});

+1 for this feature request. In my case, I need to fetch a token (which may or may not be cached) from a 3rd party REST service and send it with each request made by the resource. Intercepting the request seems like the obvious answer.

+1 for this feature request

+1

+1
I need request interceptor in resource too

@qstrahl just FYI, but what you're trying to achieve does not depend on this non-existent feature but can be achieved via $http interceptors like so:

$httpProvider.interceptors.push(
        ($injector, $q) => {
            return {
                request(config) {
                    $injector.invoke((UserService) => {
                        config.headers = config.headers || {};

                        config.headers['x-token'] = UserService.getToken();
                    });
                    return config;
                }
            };
        }
    );

@gitnik You misunderstand my problem. Your suggestion will call the interceptor for every request ever made by $http. That's exactly what I want to avoid. I need the interceptor on the resource method only.

Ah sorry misread some parts of your question

No worries

On Fri, 13 Nov 2015, 17:07 Nik Karbaum [email protected] wrote:

Ah sorry misread some parts of your question

—
Reply to this email directly or view it on GitHub
https://github.com/angular/angular.js/issues/5146#issuecomment-156571970
.

+1

You get my vote for this one +1

+1 for basic feature asked from 3 years !!

+1

+1

+1

+1

Will this be implemented anytime ?

It will be in 1.6.x (not sure about 1.5.x). You can track #13273 as well.

@gkalpak That's awesome to hear, big thanks to everyone involved :D

Was this page helpful?
0 / 5 - 0 ratings