20

In an Angular Service I'm using HttpParams to send a string to the service:

get(phone: string): Observable<PhoneSearchResponse> {
    let params = new HttpParams();
    params = params.append("phone", phone); // sending "+123456"
    return this._http.get<PhoneSearchResponse>(`${this._apiurl}/Get`, { params: params });
}

When calling get() with +123456 as parameter I'll get 123456 in the receiving service. So somewhere on the way the + gets converted to a space.

Do I need to escape HttpParams to get them unchanged to the service?

If it matters, the backend is an asp.net core project. The called code in the controller:

[HttpGet("[action]")]
public async Task<JsonResult> Get(string phone) // receiving " 123456"
{
  return Json(await PhoneSearchLogic.GetAsync(phone));
}

[Update] Very good explanation by Noémi Salaün - but I wonder if changing the parameters is expecteded behaviour "by design"? Or is the problem the ASP.NET Core controller, which should not unescape the + sign (and others)?

Sam
  • 28,421
  • 49
  • 167
  • 247
  • 1
    Possible duplicate of [Angular url plus sign converting to space](https://stackoverflow.com/questions/45428842/angular-url-plus-sign-converting-to-space) – Hugo Noro Mar 22 '18 at 21:33
  • Seems related, yes. But shouldn't HttpParams escape the + sign? – Sam Mar 22 '18 at 22:37

4 Answers4

34

As you can see in the source code common/http/src/params.ts, HttpParams uses a default encoder HttpUrlEncodingCodec.

HttpUrlEncodingCodec uses the native encodeURIComponent function but then un-encodes some symbole to meet the RFC specification (not followed by the native JS implementation).

So if you want to keep your + symbol encoded you can encode it manually before using HttpParams, or you can override the HttpParameterCodec with your own implementation and pass it through the HttpParamOptions.encoder attribute.

All this was better explained in the now deprecated Http service. With its UrlSearchParams and the QueryEncoder.

As you can see in the source code http/src/url_search_params.ts

By default, QueryEncoder encodes keys and values of parameters using encodeURIComponent, and then un-encodes certain characters that are allowed to be part of the query according to IETF RFC 3986: https://www.rfc-editor.org/rfc/rfc3986.

These are the characters that are not encoded: ! $ \' ( ) * + , ; A 9 - . _ ~ ? /

If the set of allowed query characters is not acceptable for a particular backend, QueryEncoder can be subclassed and provided as the 2nd argument to URLSearchParams.

Community
  • 1
  • 1
Noémi Salaün
  • 4,866
  • 2
  • 33
  • 37
  • That explains a lot - so, according to RFC specs, this replacement is "by design", even though it changes the data I get at the service end? Or is the Service the problem and should not unescape the + sign? – Sam Mar 23 '18 at 09:48
  • It doesn't change the data. If you look at the console when sending the request, you'll see the `+`. Some backend understand a `+` inside an URL, as an escaped space. Like the Angular team said, if a particular backend does not follow the RFC, you can provide your `HttpParameterCodec` that best fit your needs. – Noémi Salaün Mar 23 '18 at 10:13
  • Thanks for the follow-up, it explains a lot. I'll check if I try to 'fix' the backend or create my own encoder as parameter. – Sam Mar 23 '18 at 19:45
  • 4
    This should be in the Angular documentation. It is quite a surprise to see your + character remains unchanged when you expect it to be encoded. Thanks. – Pierre Jun 12 '19 at 19:06
11

Angular 13 still has this problem. Look at https://github.com/angular/angular/issues/11058 and https://github.com/angular/angular/pull/45111
The solution that worked for me was written by Ali Kamalizade at https://betterprogramming.pub/how-to-fix-angular-httpclient-not-escaping-url-parameters-ddce3f9b8746
The following is his solution. The same solution was suggested by Noémi Salaün in the accepted answer of this question.

import { HttpParameterCodec } from '@angular/common/http';
export class CustomHttpParamEncoder implements HttpParameterCodec {
    encodeKey(key: string): string {
        return encodeURIComponent(key);
    }
    encodeValue(value: string): string {
            return encodeURIComponent(value);
    }
    decodeKey(key: string): string {
            return decodeURIComponent(key);
    }
    decodeValue(value: string): string {
            return decodeURIComponent(value);
    }
}

You can use it when you create a HttpParams.

 new HttpParams({ encoder: new CustomHttpParamEncoder() })

EDIT: This was fixed in version 14. See https://github.com/angular/angular/blob/main/CHANGELOG.md#http-1 https://blog.angular.io/angular-v14-is-now-available-391a6db736af

rickz
  • 4,324
  • 2
  • 19
  • 30
-4

Rather a pure Javascript solution,

const string = 'text+that+has+plus+signs';

To encode safely, (please note encodeURIComponent is important as btoa can return +)

const enc = encodeURIComponent(btoa(string));
console.log(enc); //dGV4dCt0aGF0K2hhcytwbHVzK3NpZ25z

To decode and get the plain text back,

const dec = atob(decodeURIComponent(enc));    
console.log(dec); //text+that+has+plus+signs

I am not really a fan of overriding the HttpUrlEncodingCodec just to escape + signs in the post parameters.

I hope this will help someone. Cheers :)

Anjana Silva
  • 8,353
  • 4
  • 51
  • 54
-5

I ran into this issue some time ago, and finally resolved to use FormData instead of HttpParams... almost everywhere. I used to rely on FormData especially when sending files to a service, until I sent a string with the + symbol in it using HttpParams, and + never got to the backend; it got replaced by a space.

So just replace

let params = new HttpParams();
params = params.append("phone", phone); // sending "+123456"

with

let params = new FormData();
params.append("phone", phone);
mtchuente
  • 334
  • 5
  • 9