angular: by default not send undefined params in url

I’m submitting a…

  • Feature request

CURRENT

To be simple, I will take example for GET request. Here you have a simple method making http call with an optional parameter:

public getCity(cityId?: number){
  this.get('city', { params: {city: cityId}})
}

cityId? is optional, so you can call method like this.getCity(), if you do so angular will make request to this url : http://localhost/city?city=undefined

EXPECTED

I think avoid sending undefined params (not including them in HttpParams if they are undefined) should be most common use case because we can check this on backend. If params aren’t present == undefined.

MOTIVATION

Advantages of this is : maybe URL have limits and sending essential/useful data we really need is important. I don’t understand why send them by default if they are undefined. Something like Include.NON_NULL in jackson. What you think about not send them by default ? or add option in options{} object just to avoid this big codes:

let cleanedParams = new HttpParams();
req.params.keys().forEach(x => {
  if(req.params.get(x) != undefined)
    cleanedParams = cleanedParams.append(x, req.params.get(x));
})

const clonedRequest = req.clone({
  params:cleanedParams
});

return next.handle(clonedRequest)

Otherwise yes actually we can fortunately workaround by interceptor or with :

public getCity(cityId?: number){
  let params = {};
  if(cityId)
    params.city=cityId;
  this.get('city', params)
}

But when you have 10/20 params or 500/1k http request you are happy if you avoid this condition 😄

I didn’t think about others backend endpoint so will think within my use case. Idea should only react with undefined params not with null params. For instance my endpoint in java if I expect to receive @PathVariable Long cityId I can receive null but not undefined.

Environment

Angular 5.0.2

Browser tested:

  • Chrome console (version 21.11.2017)

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 57
  • Comments: 22 (3 by maintainers)

Most upvoted comments

Since I use optional parameters quite a bit, I decided to write my own helper function which I have listed below in case it proves useful to others.

In a file named http-params.ts:

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

export function createHttpParams(params: {}): HttpParams {
    let httpParams: HttpParams = new HttpParams();
    Object.keys(params).forEach(param => {
        if (params[param]) {
            httpParams = httpParams.set(param, params[param]);
        }
    });

    return httpParams;
}

Then, when I need to create some HTTP parameters, I use it as follows:

import { HttpParams } from '@angular/common/http';
import { createHttpParams } from '../lib/http-params';
...
@Injectable()
export class MyApiService {
  someApiMethod(param1: string, param2?: string, param3?: string): Observable<Member[] | {}> {
    const params: HttpParams = createHttpParams({ param1: param1, param2: param2, param3: param3 });
    ...
  }
...
}

For future readers, this can also be handled generically as an HTTP Interceptor:


import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

/**
 * Strip out any `undefined` parameters in the query string.
 */
@Injectable()
export class StripUndefinedParams implements HttpInterceptor {

    /**
     * Iterate through query parameters and remove all those that are `undefined`.
     *
     * @param request The incoming request.
     * @param next The next handler.
     * @returns The handled request.
     */
    public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        let params = request.params;
        for (const key of request.params.keys()) {
            if (params.get(key) === undefined) {
                params = params.delete(key, undefined);
            }
        }
        request = request.clone({ params });
        return next.handle(request);
    }
}

Duplicate of #18567

This definitely should be the default behavior.

FWIW- I don’t think it should be the framework’s responsibility to protect us from passing invalid values. If I pass an undefined by accident, I would much rather it show up in the URL so I know exactly what I’m doing wrong. If these values disappear ‘magically’, and I’m actually expecting them, I face a much harder task tracing the origin.

I would at least expect a property inside HttpParamsOptions.

For example: new HttpParams({ fromObject: { a, b }, stripUndefined: true }) }

Default behaviour might be that it does not strip undefined.

An interceptor works, but shouldn’t be needed for something this simple 😃

We too seek that functionality (and believe it should come out of the box)

My solution is to add a new util method to remove “empty” object properties. And it can be used not only for http params.

/**
 * Removes empty properties from an object
 *
 * @param object is any object
 * @returns a clone of the given object with no empty (`undefined` or `null`) properties
 **/
static removeEmptyProperties(object) {
  return _.pick(object, value => value !== null && value !== undefined);
}

We use the lodash library in our project. Here I used the _.pick() method to make it more clear.

That’s how I can use it:

find$(searchItem): Observable<any> {
  const url = `${this.URL_BASE}/find/`;
  const params = AppUtil.removeEmptyProperties(searchItem);

  return this.http.get<any>(url, {params});
}

The interceptor workaround appears to no longer work, since any undefined value is now stored as an actual string value "undefined" making the comparison fail.

const params = new HttpParams({fromObject: {a: undefined, b: null}});

console.log(params.get("a"), params.get("a") === undefined); // shows the string "undefined" and comparison fails
console.log(params.get("b"), params.get("b") === null); // shows the string "null" and comparison fails

That is, the value has been already changed to a string before the HttpInterceptor has a chance to see the actual initial value to remove any undefined or null values.

The change was possibly introduced with this commit https://github.com/angular/angular/blame/ec8b52af69a2e04a7b077eb4d4309870ba40e273/packages/common/http/src/params.ts#L168 , which will ensure all values stored with HttpParams are strings.