angular / angular

Deliver web apps with confidence 🚀

Home Page:https://angular.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

HttpParams do not encoode a stringified object correctly

demiro opened this issue · comments

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X ] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

const includeStr = JSON.stringify({include: 'match-timeline-events'});
const params: HttpParams = new HttpParams().set('filter', includeStr);

generates this in http.get

http://localhost:3000/api/...?filter=%7B%2‌​2include%22:%22match‌​-timeline-events%22%‌​7D

Expected behavior

I shoul get this:

http://localhost:3000/api/...?filter=%7B%2‌​2include%22%3A%22match‌​-timeline-events%22%‌​7D

What is the motivation / use case for changing the behavior?

being able to use more complex url params than just simple string key/value pairs

it seems this forcefully denies encoding of the colon

.replace(/%3A/gi, ':')

Environment


Angular version: 4.3.6
Browser:
- [x ] Chrome (desktop) version 60.0.3112.101
 
For Tooling issues:
- Node version: 6.10.2
- Platform:  Windows

I created a PR to solve this here: #19710 .


I also created a package to solve it (at least while it is reviewed / merged) for the case of HttpClient. It's a simple drop-in replacement that modifies the minimum to fix the problem. It works with just changing one import.

Check it here: https://github.com/tiangolo/ngx-http-client

Just to add, same problem lies with plus, which is not encoded and on the server side usually decoded as space. ISO Date representation therefore becomes not legible to Date object.

Example: 2019-01-01T00:00:00.000+01:00 becomes 2019-01-01T00:00:00.000 01:00 which in case of Mongoose becomes Error('Cast to date failed for value...')

Is there any workaround for this? Angular 7

@rgunczer , I know it is ugly, but we are currently using this:

utils.ts:

/**
 * Encode a dictionary of query params into a string
 *
 * Example:
 *
 *   > encodeParams({ a: 1, b: 2 })
 *   "a=1&b=2"
 *
 * Use it instead of passing `params` to `HttpClient.get` and `HttpClient.post`,
 * because the latter have a known issue with encoding '+':
 * https://github.com/angular/angular/issues/18884
 */
export function encodeParams(params: {[key: string]: string}): string {
  if (!params)
    return '';
  const ret = [];
  for (const name in params) {
    if (params.hasOwnProperty(name)) {
      const value = params[name];
      if (value != null)
        ret.push(`${name}=${encodeURIComponentFull('' + value)}`);
    }
  }
  return ret.join('&');
}

// encodeURIComponent() will not encode: ~!*()'
// https://stackoverflow.com/a/44431811
export function encodeURIComponentFull(str: string): string {
  str = encodeURIComponent(str);

  str = str.replace('(', '%28');
  str = str.replace(')', '%29');
  str = str.replace('*', '%29');
  str = str.replace('!', '%21');
  str = str.replace('~', '%7E');
  str = str.replace('\'', '%27');

  return str;
}

Usage example:

makeSomeRequest(params?: StringMap<string>): Observable<SomeResponse> {
  return this.http.get<SomeResponse>(`some-url/?${encodeParams(params)}`);
}

Hi, I've created the following custom HttpParameterCodec as a workaround:

import { HttpUrlEncodingCodec } from '@angular/common/http';
  
export class UrlParameterEncodingCodec extends HttpUrlEncodingCodec {

  encodeKey(key: string): string {
    return encodeURIComponent(key);
  }

  encodeValue(value: string): string {
    return encodeURIComponent(value);
  }

}

And I use it like this:

export class AppComponent {
  static readonly ISO_8601_DATE_FORMAT = 'yyyy-MM-ddTHH:mm:ss.sssZ';
  static readonly URL_PARAMETER_ENCODING_CODEC = new UrlParameterEncodingCodec();

...

  query() {
    this.queryService.execute(new HttpRequest('GET', '/api/endpoint', {
      params: new HttpParams({
        fromObject: {
          datePublished__lt: formatDate(Date.now(), AppComponent.ISO_8601_DATE_FORMAT, 'en-US'),
          fields: 'amount'
        },
        encoder: AppComponent.URL_PARAMETER_ENCODING_CODEC
      }),
      headers: new HttpHeaders({
        'X-Order': 'datePublished'
      })
    })).subscribe(

...

    );
  }

...

}

@mruzicka 's solution worked great for me in Angular 6.

I'm going to close this as a duplicate of #11058, which tracks the chaos of replacements that HttpUrlEncodingCodec performs.

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.