Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

common/http: HttpClient and params #18012

Closed
cexbrayat opened this issue Jul 8, 2017 · 51 comments
Closed

common/http: HttpClient and params #18012

cexbrayat opened this issue Jul 8, 2017 · 51 comments

Comments

@cexbrayat
Copy link
Member

cexbrayat commented Jul 8, 2017

I'm submitting a ...


[ ] Regression (behavior that used to work and stopped working in a new release)
[ ] Bug report 
[x] 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

If I follow correctly, we have to do:

const params = new HttpParams().set('status', status);
return this.http.get(`api/users`, { params });

whereas the old Http service allowed to use a simple object literal (see https://github.com/angular/angular/blob/master/packages/http/src/interfaces.ts#L48):

return this.http.get(`api/users`, { params: { status });

It would be great to be able to do the same.

Expected behavior

return this.http.get(`api/users`, { params: { status });

Please tell us about your environment


Angular version: 4.3.0-rc.0

cc @alxhub @IgorMinar

@jnizet
Copy link
Contributor

jnizet commented Jul 8, 2017

HttpParams is immutable. set() creates and returns a new HttpParams instance, without mutating the instance on which set() is called. So the code should be

const params = new HttpParams().set('status', status);

@cexbrayat
Copy link
Member Author

@jnizet Right, I updated the issue

@avatsaev
Copy link
Contributor

Why can't we just use an arbitrary object? It was so much easier

@dmytro-krekota
Copy link

I think it will be good if you add more info about immutable HttpParams in api doc: https://angular.io/api/common/http/HttpParams .
And also add examples for several arguments like:

let httpParams = new HttpParams().set('firstName', 'John').set('lastName', 'Doe');
this.http.get('/api/people', {params: httpParams});

=> /api/people?firstName=John&lastName=Doe

Because working with HttpParams for me was confused.

@CDDelta
Copy link

CDDelta commented Jul 19, 2017

@DmitryKrekota if you need multiple params on object use .append('param', 'value').

@PodaruDragos
Copy link

@CDDelta how would you do this if you have an N number of parameters going into the get request ?before with the old http you could have just used a for in loop. I am assuming this is not the case anymore.

@CDDelta
Copy link

CDDelta commented Jul 20, 2017

@PodaruDragos HttpParams is immutable so in order to use it with a loop you will have to do something like this:

let params = new HttpParams();
for (let i = 0; i < 10; i++) {
     params = params.append(i.toString(), i.toString());
}

@fpl1976
Copy link

fpl1976 commented Jul 24, 2017

I'm having a similar problem:

const params = new HttpParams().set('lang', lang);
return this.http.get(URLS.TRANSLATIONS, { params: params })
.distinctUntilChanged();

And the request I get is:

/api/Globalization/GetTranslations?updates=%7B%22param%22:%22lang%22,%22value%22:%22en%22,%22op%22:%22s%22%7D&cloneFrom=%7B%22updates%22:null,%22cloneFrom%22:null,%22encoder%22:%7B%7D,%22ma

Obviously not working

@anthonysubramani
Copy link

@CDDelta thank you. This needs to explained clearly in the docs, where they give example to set only one parameter(which will be a rare case in real time) and its not mentioned as immutable. docs link

@Agrumas
Copy link

Agrumas commented Aug 18, 2017

Noticed this inconvenience too, here is the snippet to convert an object to HttpParams.

function toHttpParams(params) {
    return Object.getOwnPropertyNames(params)
                 .reduce((p, key) => p.set(key, params[key]), new HttpParams());
}
// Example:
toHttpParams({firstName: 'John', lastName: 'Doe'});

@cexbrayat
Copy link
Member Author

This should be soon fixed by #18490

@demiro
Copy link

demiro commented Aug 25, 2017

I have a problem with not easy key value pair... what if I wanted to set for a value an object?

I am having this problem:

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

when I get the headers thens et to
(urlencoded): filter:%7B%22include%22:%22match-timeline-events%22%7D

but for some reason, the : doesn't get encoded and the string should've been
(urlencoded): filter:%7B%22include%22%3A%22match-timeline-events%22%7D

any ideas how to solve this?

@cexbrayat
Copy link
Member Author

This was fixed by #18490 and released in 5.0.0-beta.6

@gregbown
Copy link

gregbown commented Sep 12, 2017

Can anyone enlighten me as to why one should parse parameters into this new HttpParams instance only to convert it back into a string in the POST body? It seems more logical to just convert my parameters into a single string initially, rather than creating some cumbersome immutable object multiple times. What value does the HttpParams add? It seems that in order to use it throughout an application, I would need to extend it.

@AlexandrBukhtatyy
Copy link

If i use loop for append parameters to HttpParams it copy a param many times!

@rachitd-ezdi
Copy link

Can anyone tell me the basic difference of set() and append() method??? What one should prefer over other???

@compass-yan
Copy link

@Agrumas Thanks a lot ,This solve my question.To be honest,I still confuse about the work of httpParams .

@alekseytimonin
Copy link

alekseytimonin commented Jan 9, 2018

I have the same issue. I just switched to Angular 5 and adjusted my code accordingly:

    let params = new HttpParams();
    params.set('firstName', customerFilter.firstName);
    params.set('lastName', customerFilter.lastName);
                
    return this.http.get<CustomersData>(settings.url, {
        params: params, 
        responseType: 'json'})
        .toPromise()
        .then(response => response)
        .catch(err => this.handleError(err));

But, result url doesn't contain parameters!
I tried to change set method to append... the same issue

Any idea how to fix that?

@jnizet
Copy link
Contributor

jnizet commented Jan 9, 2018

@alekseytimonin HttpParams is immutable. Its set() method returns a new HttpParams, without mutating the original one. Your code should be

let params = new HttpParams();
params = params.set('firstName', customerFilter.firstName);
params = params.set('lastName', customerFilter.lastName);

Or simply

const params = new HttpParams()
  .set('firstName', customerFilter.firstName)
  .set('lastName', customerFilter.lastName);

Also note that you can simply pass a simple JavaScript object now.

@kevinding0218
Copy link

I am having the same issue with @fpl1976, simply put the the params but the generated request url looks so weird like "updates=%7B%22param%22:%22lang%22,%22value%22:%22en%22,%22op%22:%22s%22%7D&cloneFrom=%7B%22updates%22:null,%22cloneFrom%22:null,%22encoder%22:%7B%7D"
for example:
let httpParams = new HttpParams().set('firstName', 'John').set('lastName', 'Doe');
this.http.get('/api/people', { params: httpParams });
should generate request like => "/api/people ? firstName = John & lastName= Doe"
however generate request like => "/api/people ? updates=%7B%22param%22:%22lang%22,%22value%22:%22en%22,%22op%22:%22s%22%7D&cloneFrom=%7B%22updates%22:null,%22cloneFrom%22:null,%22encoder%22:%7B%7D"

currently I have to remove the usage of HttpParams and generate the request in string but it feels bad by doing that..
Any suggestion or idea to fix that would be really appreciated!

Best

@fpl1976
Copy link

fpl1976 commented Jan 18, 2018

@kevinding0218 with angular version 5 or higher it is solved

@kevinding0218
Copy link

@fpl1976 , thank you! However, I am actually using Angular 5.0.0 and this is what happened to me, is there any part of the code that should be changed?

@fpl1976
Copy link

fpl1976 commented Jan 18, 2018

@kevinding0218 I'm using 5.1.0, try to upgrade

@anjmao
Copy link

anjmao commented Jan 31, 2018

@cexbrayat
new HttpParams still have strange implementation

const params = new HttpParams({
  fromObject: {
     pageNumber: 1,
     dateFrom: new Date(),
     onlyActive: true
  }
})

it will show typescript error because fromObject is just [param: string]: string | string[]; map and value should be string, so we need to convert bool and number to string.

Now developers will need to write utils to simply convert plain object to http params.

import { HttpParams } from '@angular/common/http';

export function toHttpParams(obj: Object): HttpParams {
    let params = new HttpParams();
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            const val = obj[key];
            if (val !== null && val !== undefined) {
                params = params.append(key, val.toString());
            }
        }
    }
    return params;
}
const myListFilter = {
    pageNumber: 1,
    contractId: '#aaab',
    onlyActive: true
};

const params = toHttpParams(myListFilter); // oh finally

Would it be possible to allowing to set number, boolean, datetime?

@rochapablo
Copy link

rochapablo commented Feb 7, 2018

Still not working on 5.2.0;

Doing the follow code, nothing happens, not even error

let params = new HttpParams();
// params.set // noop
// params.append // noop
// HttpParams({ fromObject: ... // noop
http.get(`http://...`, { params: params })

But just mounting the query, it works

http.get(`http://...?page=1&user=1`)

So, it seems that the problem it's with http.get

In resume

This is not woking (no request is fired)

const query = new HttpParams();
Object.keys(params).forEach(key => query.append(key, params[key].toString()));
const options = { params: query };
http.get(`http://...`, options)

This is working

const tmp = [];
Object.keys(params).forEach(key => tmp.push(`${key}=${params[key]}`));
const query = tmp && tmp.length ? `?${tmp.join('&')}` : '';
http.get(`http://...${query}`)

@kevinding0218
Copy link

@rochapablo , I figured out why it's not working, the HttpParams seems only work with HttpClient not Http, so in your service when inject the http you can try to refer HttpClient(import { HttpClient } from '@angular/common/http';) instead of Http

@rochapablo
Copy link

But it's what I'm doing =/

import { HttpClient, HttpParams } from '@angular/common/http';

constructor(protected http: HttpClient) {

@kevinding0218
Copy link

@rochapablo , when you mean "no error", could you check if the request was successfully responded or some error code returned (e.g Chrome Network tab) if possible, or did it even hook up the api/service method if you could set a debug break point there?

@jwhijazi
Copy link

@CDDelta ... You are right.
Because the httpParams is immutable; set() or append() will create a new object.
I wrote the generic function to extract the fields form a defined object and returns httpParams object.

private getHttpParams(params: any): HttpParams { let _httpParams = new HttpParams(); if (params) { for (let prop in params) { if (params.hasOwnProperty(prop)) { let val = params[prop].toString(); _httpParams = _httpParams.append(prop, val); } } } return _httpParams; }

@CDDelta
Copy link

CDDelta commented Feb 12, 2018

@jwhijazi you can actually provide an object to the http client instead of a HttpParams now. You will need to update Angular though.

@jwhijazi
Copy link

@CDDelta
Currently I'm using angular version (5.2.4).
Can you tell me the next version, and if you can please provide me with an example of using objects with httpClient.
Thanks

@CDDelta
Copy link

CDDelta commented Feb 12, 2018

@jwhijazi introduced in 5.0.0

@MrAntix
Copy link

MrAntix commented Feb 12, 2018

how about a static factory method?

HttpParams.from(myObject)

@trotyl
Copy link
Contributor

trotyl commented Feb 13, 2018

@MrAntix Why wrap an object to HttpParams while it can be used directly?

@MrAntix
Copy link

MrAntix commented Feb 14, 2018

@trotyl I guess a use case would be where you have an object you want to start with, and then append

var params = HttpParams.from(myObject).append('index', '10').append('count', '100');

Fact is the API is pretty tough, everything has to be a string - which I know is the ultimate goal, but having something to handle serialisation generally would be helpful. If not, a global extension point which is easily implemented would be good. In the meantime we can inject our own service to map from our own objects to HttpParams. Fact is, I've become used to the framework handling this - guess I'm getting lazy in my old age :D

Totally fine with the immutable ideal btw.

@trotyl
Copy link
Contributor

trotyl commented Feb 14, 2018

@MrAntix Objects can also be changed quickly:

var params = Object.assign({}, myObject, { index: '10', count, '100' })

Or just mutate it:

Object.assign(myObject, { index: '10', count, '100' })

The immutability may be a good restriction for HttpIntercepters, but could gain little benefit for end user.

For supporting non-string types, there's some discussion in #20316 (and linked PRs).

@lukaVarga
Copy link

HttpParams should really allow for number and boolean params. It really makes no sense for us to make use of the default HttpParams as we have to kiss type-safety goodbye. Instead, if we wish to keep type safety of our params, we have to write our own utils to convert a properly type-annotated params object to [param: string]: string | string[] type. This is a really core util that should be handled under the hood.

@melroy89
Copy link

melroy89 commented May 1, 2018

I also would like to see a toObject() & toArray() functions besides the .toString(), since I like to have key/value pairs (eg. { bla: 3, saa: 5} instead of bla=3&saa=5).

So who want to help me to add this to the code archive?

toObject() {
   return this.keys().reduce((obj, key) => {
       obj[key] = this.get(key);
       return obj;
   }, {});
}

For an array use:

toArray() {
   return this.keys().map(x => ({ [x]: this.get(x) }));
}

@lordgreg
Copy link

lordgreg commented May 3, 2018

Can someone explain how this is still an issues? How can it be, this use-case doesn't work:

const p = new HttpParams();
p.append('foo', 'bar');
p.append('abc', '123');

p should now have two items in map, not 0.

@CDDelta
Copy link

CDDelta commented May 3, 2018

@lordgreg its not an issue. HttpParams is immutable, when you use append() it returns a new instance of HttpParams. Read above for more info.

Also FYI you should be able to just pass an object to HttpClient methods if you are on 5.0.0 or above.

@lordgreg
Copy link

lordgreg commented May 3, 2018

@CDDelta thank you for your response. However, if my existingHttpParams: HttpParams already includes 3 parameters and I do

console.log(existingHttpParams.append('foo', 'bar'));

What would you expect to output? 3 previous params + 1 new in map. Instead, it now has cloneFrom which is the previously used httpParams variable, updates, which includes our foo and map, which is null.

Does this make sense? Of course not. Is there a use-case where this would make sense? I cannot find a single.

Does toString() output proper params string? Yes.

@melroy89
Copy link

melroy89 commented May 3, 2018

@lordgreg This is by design the case. Try it like this:

const p = new HttpParams();
p = p.append('foo', 'bar');
p = p.append('abc', '123');

Or:

const p = new HttpParams().set('foo', 'bar').set('abc', '123');

@maks-humeniuk
Copy link

Any news regarding number and boolean support? Or we just end up using excessive convert utils?

@raghul-selsoft
Copy link

to add the params in this following lines
return this.http.post(BaseUrl, { headers: headers, params: { fromDate: 'abc', } })

@DanielFrugoni
Copy link

Im using let httpParams = new HttpParams().set('name', term);
Only work if I type the complit name.
Ex: Peter ... Ok
Pe ... Not found

Any help ?

@Femina
Copy link

Femina commented Feb 26, 2019

how to append custom headers and also params with get request of @angular/common/http ?

@rachitd-ezdi
Copy link

rachitd-ezdi commented Feb 26, 2019 via email

@ButtonWire
Copy link

How can I get below format with HttpParams?

?parameters=%7B%22POPID %22:%22 9999991247%22,%22FormType%22:1%7D

without encoding
?parameters={POPID :9999991247,FormType:1 }

@maks-humeniuk
Copy link

maks-humeniuk commented May 29, 2019

@ButtonWire, I think you'll need to play with native JavaScript encodeURIComponent() / decodeURIComponent() methods.

@mgodu01
Copy link

mgodu01 commented Aug 5, 2019

similar problem, I am using the interceptor, tried to remove one of the key from the params, it is not getting deleted. I know you can assign a value to read-only object.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { 
request.params = request.params.delete('test'); // this can't be done, please suggest how to do that
}

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests