fix(common): allow number or boolean as http params (#40663)

This change fixes an incompatibility between the old `@angular/http` package
and its successor (`@angular/common/http`) by re-introducing the types that were supported before.

It now allows to use number and boolean directly as HTTP params, instead of having to convert it to string first.

Before:

    this.http.get('/api/config', { params: { page: `${page}` } });

After:

    this.http.get('/api/config', { params: { page }});

`HttpParams` has also been updated to have most of its methods accept number or boolean values.

Fixes #23856

BREAKING CHANGE:

The methods of the `HttpParams` class now accept `string | number | boolean`
instead of `string` for the value of a parameter.
If you extended this class in your application,
you'll have to update the signatures of your methods to reflect these changes.

PR Close #40663
This commit is contained in:
cexbrayat
2021-02-26 12:03:50 -08:00
committed by Zach Arend
parent 383d226118
commit 91cdc11aa0
6 changed files with 529 additions and 272 deletions
+1 -1
View File
@@ -72,7 +72,7 @@ The `get()` method takes two arguments; the endpoint URL from which to fetch, an
options: {
headers?: HttpHeaders | {[header: string]: string | string[]},
observe?: 'body' | 'events' | 'response',
params?: HttpParams|{[param: string]: string | string[]},
params?: HttpParams|{[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>},
reportProgress?: boolean,
responseType?: 'arraybuffer'|'blob'|'json'|'text',
withCredentials?: boolean,
+126 -126
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13 -9
View File
@@ -101,10 +101,13 @@ function standardEncoding(v: string): string {
.replace(/%3F/gi, '?')
.replace(/%2F/gi, '/');
}
function valueToString(value: string|number|boolean): string {
return `${value}`;
}
interface Update {
param: string;
value?: string;
value?: string|number|boolean;
op: 'a'|'d'|'s';
}
@@ -121,7 +124,7 @@ export interface HttpParamsOptions {
fromString?: string;
/** Object map of the HTTP parameters. Mutually exclusive with `fromString`. */
fromObject?: {[param: string]: string|ReadonlyArray<string>};
fromObject?: {[param: string]: string|number|boolean|ReadonlyArray<string|number|boolean>};
/** Encoding codec used to parse and serialize the parameters. */
encoder?: HttpParameterCodec;
@@ -208,7 +211,7 @@ export class HttpParams {
* @param value The new value to add.
* @return A new body with the appended value.
*/
append(param: string, value: string): HttpParams {
append(param: string, value: string|number|boolean): HttpParams {
return this.clone({param, value, op: 'a'});
}
@@ -217,7 +220,8 @@ export class HttpParams {
* @param params parameters and values
* @return A new body with the new value.
*/
appendAll(params: {[param: string]: string|string[]}): HttpParams {
appendAll(params: {[param: string]: string|number|boolean|ReadonlyArray<string|number|boolean>}):
HttpParams {
const updates: Update[] = [];
Object.keys(params).forEach(param => {
const value = params[param];
@@ -226,7 +230,7 @@ export class HttpParams {
updates.push({param, value: _value, op: 'a'});
});
} else {
updates.push({param, value, op: 'a'});
updates.push({param, value: value as (string | number | boolean), op: 'a'});
}
});
return this.clone(updates);
@@ -238,7 +242,7 @@ export class HttpParams {
* @param value The new value.
* @return A new body with the new value.
*/
set(param: string, value: string): HttpParams {
set(param: string, value: string|number|boolean): HttpParams {
return this.clone({param, value, op: 's'});
}
@@ -249,7 +253,7 @@ export class HttpParams {
* @return A new body with the given value removed, or with all values
* removed if no value is specified.
*/
delete(param: string, value?: string): HttpParams {
delete(param: string, value?: string|number|boolean): HttpParams {
return this.clone({param, value, op: 'd'});
}
@@ -293,13 +297,13 @@ export class HttpParams {
case 'a':
case 's':
const base = (update.op === 'a' ? this.map!.get(update.param) : undefined) || [];
base.push(update.value!);
base.push(valueToString(update.value!));
this.map!.set(update.param, base);
break;
case 'd':
if (update.value !== undefined) {
let base = this.map!.get(update.param) || [];
const idx = base.indexOf(update.value);
const idx = base.indexOf(valueToString(update.value));
if (idx !== -1) {
base.splice(idx, 1);
}
+25 -1
View File
@@ -51,10 +51,34 @@ import {toArray} from 'rxjs/operators';
expect(req.request.headers.get('X-Option')).toEqual('true');
req.flush({});
});
it('with params', done => {
it('with string params', done => {
client.get('/test', {params: {'test': 'true'}}).subscribe(() => done());
backend.expectOne('/test?test=true').flush({});
});
it('with an array of string params', done => {
client.get('/test', {params: {'test': ['a', 'b']}}).subscribe(() => done());
backend.expectOne('/test?test=a&test=b').flush({});
});
it('with number params', done => {
client.get('/test', {params: {'test': 2}}).subscribe(() => done());
backend.expectOne('/test?test=2').flush({});
});
it('with an array of number params', done => {
client.get('/test', {params: {'test': [2, 3]}}).subscribe(() => done());
backend.expectOne('/test?test=2&test=3').flush({});
});
it('with boolean params', done => {
client.get('/test', {params: {'test': true}}).subscribe(() => done());
backend.expectOne('/test?test=true').flush({});
});
it('with an array of boolean params', done => {
client.get('/test', {params: {'test': [true, false]}}).subscribe(() => done());
backend.expectOne('/test?test=true&test=false').flush({});
});
it('with an array of params of different types', done => {
client.get('/test', {params: {'test': [true, 'a', 2] as const}}).subscribe(() => done());
backend.expectOne('/test?test=true&test=a&test=2').flush({});
});
it('for an arraybuffer', done => {
const body = new ArrayBuffer(4);
client.get('/test', {responseType: 'arraybuffer'}).subscribe(res => {
+103 -5
View File
@@ -37,42 +37,120 @@ import {HttpParams} from '@angular/common/http/src/params';
});
describe('lazy mutation', () => {
it('should allow setting parameters', () => {
it('should allow setting string parameters', () => {
const body = new HttpParams({fromString: 'a=b'});
const mutated = body.set('a', 'c');
expect(mutated.toString()).toEqual('a=c');
});
it('should allow appending parameters', () => {
it('should allow setting number parameters', () => {
const body = new HttpParams({fromString: 'a=b'});
const mutated = body.set('a', 1);
expect(mutated.toString()).toEqual('a=1');
});
it('should allow setting boolean parameters', () => {
const body = new HttpParams({fromString: 'a=b'});
const mutated = body.set('a', true);
expect(mutated.toString()).toEqual('a=true');
});
it('should allow appending string parameters', () => {
const body = new HttpParams({fromString: 'a=b'});
const mutated = body.append('a', 'c');
expect(mutated.toString()).toEqual('a=b&a=c');
});
it('should allow appending all parameters', () => {
it('should allow appending number parameters', () => {
const body = new HttpParams({fromString: 'a=b'});
const mutated = body.append('a', 1);
expect(mutated.toString()).toEqual('a=b&a=1');
});
it('should allow appending boolean parameters', () => {
const body = new HttpParams({fromString: 'a=b'});
const mutated = body.append('a', true);
expect(mutated.toString()).toEqual('a=b&a=true');
});
it('should allow appending all string parameters', () => {
const body = new HttpParams({fromString: 'a=a1&b=b1'});
const mutated = body.appendAll({a: ['a2', 'a3'], b: 'b2'});
expect(mutated.toString()).toEqual('a=a1&a=a2&a=a3&b=b1&b=b2');
});
it('should allow appending all number parameters', () => {
const body = new HttpParams({fromString: 'a=1&b=b1'});
const mutated = body.appendAll({a: [2, 3], b: 'b2'});
expect(mutated.toString()).toEqual('a=1&a=2&a=3&b=b1&b=b2');
});
it('should allow appending all boolean parameters', () => {
const body = new HttpParams({fromString: 'a=true&b=b1'});
const mutated = body.appendAll({a: [true, false], b: 'b2'});
expect(mutated.toString()).toEqual('a=true&a=true&a=false&b=b1&b=b2');
});
it('should allow appending all parameters of different types', () => {
const body = new HttpParams({fromString: 'a=true&b=b1'});
const mutated = body.appendAll({a: [true, 0, 'a1'] as const, b: 'b2'});
expect(mutated.toString()).toEqual('a=true&a=true&a=0&a=a1&b=b1&b=b2');
});
it('should allow deletion of parameters', () => {
const body = new HttpParams({fromString: 'a=b&c=d&e=f'});
const mutated = body.delete('c');
expect(mutated.toString()).toEqual('a=b&e=f');
});
it('should allow deletion of parameters with specific string value', () => {
const body = new HttpParams({fromString: 'a=b&c=d&e=f'});
const notMutated = body.delete('c', 'z');
expect(notMutated.toString()).toEqual('a=b&c=d&e=f');
const mutated = body.delete('c', 'd');
expect(mutated.toString()).toEqual('a=b&e=f');
});
it('should allow deletion of parameters with specific number value', () => {
const body = new HttpParams({fromString: 'a=b&c=1&e=f'});
const notMutated = body.delete('c', 2);
expect(notMutated.toString()).toEqual('a=b&c=1&e=f');
const mutated = body.delete('c', 1);
expect(mutated.toString()).toEqual('a=b&e=f');
});
it('should allow deletion of parameters with specific boolean value', () => {
const body = new HttpParams({fromString: 'a=b&c=true&e=f'});
const notMutated = body.delete('c', false);
expect(notMutated.toString()).toEqual('a=b&c=true&e=f');
const mutated = body.delete('c', true);
expect(mutated.toString()).toEqual('a=b&e=f');
});
it('should allow chaining of mutations', () => {
const body = new HttpParams({fromString: 'a=b&c=d&e=f'});
const mutated = body.append('e', 'y').delete('c').set('a', 'x').append('e', 'z');
expect(mutated.toString()).toEqual('a=x&e=f&e=y&e=z');
});
it('should allow deletion of one value of a parameter', () => {
it('should allow deletion of one value of a string parameter', () => {
const body = new HttpParams({fromString: 'a=1&a=2&a=3&a=4&a=5'});
const mutated = body.delete('a', '2').delete('a', '4');
expect(mutated.getAll('a')).toEqual(['1', '3', '5']);
});
it('should allow deletion of one value of a number parameter', () => {
const body = new HttpParams({fromString: 'a=0&a=1&a=2&a=3&a=4&a=5'});
const mutated = body.delete('a', 0).delete('a', 4);
expect(mutated.getAll('a')).toEqual(['1', '2', '3', '5']);
});
it('should allow deletion of one value of a boolean parameter', () => {
const body = new HttpParams({fromString: 'a=false&a=true&a=false'});
const mutated = body.delete('a', false);
expect(mutated.getAll('a')).toEqual(['true', 'false']);
});
it('should not repeat mutations that have already been materialized', () => {
const body = new HttpParams({fromString: 'a=b'});
const mutated = body.append('a', 'c');
@@ -101,10 +179,30 @@ import {HttpParams} from '@angular/common/http/src/params';
const body = new HttpParams({fromObject: {a: '', b: '2', c: '3'}});
expect(body.toString()).toBe('a=&b=2&c=3');
});
it('should stringify array params', () => {
it('should stringify string array params', () => {
const body = new HttpParams({fromObject: {a: '', b: ['21', '22'], c: '3'}});
expect(body.toString()).toBe('a=&b=21&b=22&c=3');
});
it('should stringify number params', () => {
const body = new HttpParams({fromObject: {a: '', b: 2, c: 3}});
expect(body.toString()).toBe('a=&b=2&c=3');
});
it('should stringify number array params', () => {
const body = new HttpParams({fromObject: {a: '', b: [21, 22], c: 3}});
expect(body.toString()).toBe('a=&b=21&b=22&c=3');
});
it('should stringify boolean params', () => {
const body = new HttpParams({fromObject: {a: '', b: true, c: 3}});
expect(body.toString()).toBe('a=&b=true&c=3');
});
it('should stringify boolean array params', () => {
const body = new HttpParams({fromObject: {a: '', b: [true, false], c: 3}});
expect(body.toString()).toBe('a=&b=true&b=false&c=3');
});
it('should stringify array params of different types', () => {
const body = new HttpParams({fromObject: {a: ['', false, 3] as const}});
expect(body.toString()).toBe('a=&a=false&a=3');
});
it('should stringify empty array params', () => {
const body = new HttpParams({fromObject: {a: '', b: [], c: '3'}});
expect(body.toString()).toBe('a=&c=3');