14

On my current project I have a drupal backend that exposes rest services for my frontend. Some calls to my backend don't really like url entities to get encoded.

So my question is: how do I disable URL encoding of some parameters?

Example:

I need to call my backend with a "+"-sign between different search terms. Like so:

http://backend.com/someservice/search/?terms=search+terms+here

But angular, setup like so:

var resource = $resource(
  backendUrl + '/views/:view', {},
    {
      'search': {params:{view:'searchposts'}, isArray:true}
    }
 );

// search posts for the given terms
this.searchPosts = function(terms, limit) {
  resource.search({search:terms.join('+'), limit:limit});
};

Calls the following url:

http://backend.com/someservice/search/?terms=search%2Bterms%2Bhere

Any suggestions? Thanks!

polyclick
  • 2,704
  • 4
  • 32
  • 58

4 Answers4

9

Update: with the new httpParamSerializer in Angular 1.4 you can do it by writing your own paramSerializer and setting $httpProvider.defaults.paramSerializer.

Below only applies to AngularJS 1.3 (and older).

It is not possible without changing the source of AngularJS.

This is done by $http:

https://github.com/angular/angular.js/tree/v1.3.0-rc.5/src/ng/http.js#L1057

function buildUrl(url, params) {
      if (!params) return url;
      var parts = [];
      forEachSorted(params, function(value, key) {
        if (value === null || isUndefined(value)) return;
        if (!isArray(value)) value = [value];

        forEach(value, function(v) {
          if (isObject(v)) {
            v = toJson(v);
          }
          parts.push(encodeUriQuery(key) + '=' +
                     encodeUriQuery(v));
        });
      });
      if(parts.length > 0) {
        url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
      }
      return url;
}

encodeUriQuery uses the standard encodeUriComponent (MDN) which replaces the '+' with '%2B'

Too bad you cannot overwrite encodeUriQuery because it is a local variable inside the angular function.

So the only option I see is to overwrite window.encodeURIComponent. I've done it in an $http interceptor to minimize the impact. Note that the original function is only put back when the response comes back, so this change is global (!!) while your request is ongoing. So be sure to test if this doesn't break something else in your application.

app.config(function($httpProvider) {
  $httpProvider.interceptors.push(function($q) {
    var realEncodeURIComponent = window.encodeURIComponent;
    return {
      'request': function(config) {
         window.encodeURIComponent = function(input) {
           return realEncodeURIComponent(input).split("%2B").join("+"); 
         }; 
         return config || $q.when(config);
      },
      'response': function(config) {
         window.encodeURIComponent = realEncodeURIComponent;
         return config || $q.when(config);
      }
    };
  });
});
Pieter Herroelen
  • 5,977
  • 2
  • 29
  • 37
  • Damn, what are my options? :p – polyclick Apr 15 '14 at 17:02
  • Feels a bit hacky but hey seems to work fine :p Thanks a LOT! – polyclick Apr 16 '14 at 17:33
  • Created a github issue to find out what other options there are: https://github.com/angular/angular/issues/1339 – chrisjlee Apr 13 '15 at 20:52
  • Any idea how you'd go about writing your own paramSerializer function? I've had no luck so far. Right now I'm trying to pass params to a GET request to my REST api, and it's encoding my question mark and breaking the request. Your post is the closest I've gotten to something that might work for me, but I don't understand how to writing a function that stops that character from being encoded :( – user3281385 May 04 '16 at 15:21
4

AngularJS only runs the encodeUriQuery method if you pass your params as an extra object. If you manually create your own param string and concatenate it onto your URL then Angular won't modify it.

It's pretty simple to convert the param object yourself, here's an example: How to serialize an Object into a list of parameters?

Community
  • 1
  • 1
mer10z_tech
  • 697
  • 7
  • 12
0

While usually you don't want to do it (since on server side you can decode the request URI with a URL decode function to get back your plus signes) sometimes we want to "break" the rules.

Right now I can't actually test it (it would be helpful if you can create a jsfiddle with a test case for us to play with) but probably you should take a look at the transformRequest function of the resource object.

From the docs:

transformRequest – {function(data, headersGetter)|Array.} – transform function or an array of such functions. The transform function takes the http request body and headers and returns its transformed (typically serialized) version.

Antonio E.
  • 4,381
  • 2
  • 25
  • 35
0

It looks like the $resource code wraps this functionality in Route.prototype.setUrlParams:

  params = params || {};
  forEach(self.urlParams, function (_, urlParam) {
    val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
    if (angular.isDefined(val) && val !== null) {
      encodedVal = encodeUriSegment(val);
      url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function (match, p1) {
        return encodedVal + p1;
      });
    } else {
      url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function (match,
          leadingSlashes, tail) {
        if (tail.charAt(0) == '/') {
          return tail;
        } else {
          return leadingSlashes + tail;
        }
      });
    }
  });

Source

And it looks like the resourceFactory function creates a closure over the constructed route, so you don't have access to change this.

Do you specifically need this to be a resource object? If not, you can create a plain $http call and use a request transform as Antonio mentioned.

Joseph Yaduvanshi
  • 20,241
  • 5
  • 61
  • 69