Skip to content

Commit 096b5df

Browse files
committed
feat: 🎸 add removeEmptyValue config
Previously the url contained all synced form values - including empty values. BREAKING CHANGE: 🧨 From now on, empty control values will be removed from the URL query params.
1 parent e29c1c9 commit 096b5df

File tree

9 files changed

+163
-5
lines changed

9 files changed

+163
-5
lines changed

‎.all-contributorsrc

+9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@
2222
"ideas",
2323
"infra"
2424
]
25+
},
26+
{
27+
"login": "ritox842",
28+
"name": "Gili Yaniv",
29+
"avatar_url": "https://avatars.githubusercontent.com/u/7280441?v=4",
30+
"profile": "https://github.com/ritox842",
31+
"contributions": [
32+
"code"
33+
]
2534
}
2635
],
2736
"contributorsPerLine": 7

‎BREAKING_CHANGES.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
#v3
2+
### `removeEmptyValue`
3+
Previously the url contained all synced form values - including empty values.
4+
This behaviour caused the query params to contain keys without values and looked kind of messy.
5+
6+
This change is made for altering the default behaviour of the control -> query param binding and instead of adding redundant empty
7+
values to the URL, from now on the url will have only non-nullish values.
8+
9+
You can still get the old behaviour by overriding `removeEmptyValue` for each control def you would like to keep syncing
10+
empty values from the control to the query params.
11+
112
#v2
213

314
### `twoWay` Strategy

‎README.md

+5
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ Before updating the control with the value, the manager will parse it based on t
9090

9191
Whether to sync the initial control value in the URL. Relevant only when using the `modelToUrl` strategy.
9292

93+
### `removeEmptyValue`
94+
95+
Whether to remove empty control values from url.
96+
9397
### `parser`
9498

9599
Provide a custom parser. For example, the default `array` parser converts the value to an `array` of strings. If we need it to be an array of numbers, we can pass the following `parser`:
@@ -164,6 +168,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
164168
<table>
165169
<tr>
166170
<td align="center"><a href="https://www.netbasal.com/"><img src="https://avatars1.githubusercontent.com/u/6745730?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Netanel Basal</b></sub></a><br /><a href="https://github.com/@ngneat/bind-query-params/commits?author=NetanelBasal" title="Code">💻</a> <a href="#content-NetanelBasal" title="Content">🖋</a> <a href="https://github.com/@ngneat/bind-query-params/commits?author=NetanelBasal" title="Documentation">📖</a> <a href="#ideas-NetanelBasal" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-NetanelBasal" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
171+
<td align="center"><a href="https://github.com/ritox842"><img src="https://avatars.githubusercontent.com/u/7280441?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gili Yaniv</b></sub></a><br /><a href="https://github.com/@ngneat/bind-query-params/commits?author=ritox842" title="Code">💻</a></td>
167172
</tr>
168173
</table>
169174

‎package-lock.json

+30-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@angular/platform-browser-dynamic": "~11.0.5",
3030
"@angular/router": "~11.0.5",
3131
"lodash.set": "^4.3.2",
32+
"lodash.size": "^4.2.0",
3233
"rxjs": "~6.6.0",
3334
"tslib": "^2.0.0",
3435
"zone.js": "~0.10.2"
@@ -44,6 +45,7 @@
4445
"@ngneat/spectator": "^6.1.2",
4546
"@types/jasmine": "~3.6.0",
4647
"@types/lodash.set": "^4.3.2",
48+
"@types/lodash.size": "^4.2.6",
4749
"@types/node": "^12.11.1",
4850
"all-contributors-cli": "^6.19.0",
4951
"angular-cli-ghpages": "1.0.0-rc.1",

‎projects/ngneat/bind-query-params/src/lib/QueryParamDef.ts

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export class QueryParamDef<QueryParams = any> {
3232
return this.config.syncInitialValue || false;
3333
}
3434

35+
get removeEmptyValue() {
36+
return this.config.removeEmptyValue === undefined ? true : this.config.removeEmptyValue;
37+
}
38+
3539
serialize(controlValue: any): string | null {
3640
if (this.serializer) {
3741
return this.serializer(controlValue);

‎projects/ngneat/bind-query-params/src/lib/lib.spec.ts

+79
Original file line numberDiff line numberDiff line change
@@ -604,4 +604,83 @@ describe('BindQueryParams', () => {
604604
}));
605605
});
606606
});
607+
608+
describe('removeEmptyValue', () => {
609+
const emptyString = '';
610+
const emptyObject = {};
611+
const emptyArray: any[] = [];
612+
613+
interface EmptyValuesParams {
614+
string: string;
615+
object: string;
616+
array: boolean;
617+
}
618+
619+
it('should remove empty value from url', fakeAsync(() => {
620+
spectator = createComponent();
621+
tick();
622+
skipInitialSync(spectator);
623+
spectator.component.group = new FormGroup({
624+
string: new FormControl(emptyString),
625+
object: new FormControl(emptyObject),
626+
array: new FormControl(emptyArray),
627+
});
628+
// @ts-ignore
629+
spectator.component.bindQueryParams = spectator.component.factory
630+
.create<EmptyValuesParams>([
631+
{ queryKey: 'string', type: 'string' },
632+
{ queryKey: 'object', type: 'object' },
633+
{ queryKey: 'array', type: 'array' },
634+
])
635+
.connect(spectator.component.group);
636+
637+
tick();
638+
639+
assertRouterCall(spectator, {
640+
string: null,
641+
object: null,
642+
array: null,
643+
});
644+
645+
expect(spectator.inject(Router).navigate).not.toHaveBeenCalled();
646+
}));
647+
648+
it('should not remove empty value from url', fakeAsync(() => {
649+
spectator = createComponent();
650+
tick();
651+
skipInitialSync(spectator);
652+
spectator.component.group = new FormGroup({
653+
string: new FormControl(emptyString),
654+
object: new FormControl(emptyObject),
655+
array: new FormControl(emptyArray),
656+
});
657+
// @ts-ignore
658+
spectator.component.bindQueryParams = spectator.component.factory
659+
.create<EmptyValuesParams>([
660+
{ queryKey: 'string', type: 'string', removeEmptyValue: false },
661+
{ queryKey: 'object', type: 'object', removeEmptyValue: false },
662+
{ queryKey: 'array', type: 'array', removeEmptyValue: false },
663+
])
664+
.connect(spectator.component.group);
665+
666+
tick();
667+
// @ts-ignore
668+
const stringDef = spectator.component.bindQueryParams.getDef('string');
669+
// @ts-ignore
670+
const objectDef = spectator.component.bindQueryParams.getDef('object');
671+
// @ts-ignore
672+
const arrayDef = spectator.component.bindQueryParams.getDef('array');
673+
674+
assertRouterCall(spectator, {
675+
// @ts-ignore
676+
string: stringDef.serialize(emptyString),
677+
// @ts-ignore
678+
object: objectDef.serialize(emptyObject),
679+
// @ts-ignore
680+
array: arrayDef.serialize(emptyArray),
681+
});
682+
683+
expect(spectator.inject(Router).navigate).not.toHaveBeenCalled();
684+
}));
685+
});
607686
});

‎projects/ngneat/bind-query-params/src/lib/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type QueryParamParams<QueryParams = any> = {
1010
parser?: (value: string) => any;
1111
serializer?: (value: any) => string | null;
1212
syncInitialValue?: boolean;
13+
removeEmptyValue?: boolean;
1314
};
1415

1516
export interface BindQueryParamsOptions {

‎projects/ngneat/bind-query-params/src/lib/utils.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
import { AbstractControl } from '@angular/forms';
22
import { ParamDefType, ResolveParamsOption } from './types';
33
import { QueryParamDef } from './QueryParamDef';
4+
import size from 'lodash.size';
5+
6+
function isEmptyValue(def: QueryParamDef, value: any) {
7+
switch (def.type) {
8+
case 'array':
9+
case 'object':
10+
return !size(value);
11+
case 'string':
12+
return !value || value === '';
13+
default:
14+
return false;
15+
}
16+
}
417

518
export function parse(value: any, type: ParamDefType) {
619
switch (type) {
@@ -32,7 +45,15 @@ export function resolveParams(params: ResolveParamsOption | ResolveParamsOption[
3245
const result: Record<string, string | null> = {};
3346

3447
toArray.forEach(({ def, value }) => {
35-
result[def.queryKey] = def.serialize(value);
48+
let defValue: string | null;
49+
50+
if (def.removeEmptyValue) {
51+
defValue = isEmptyValue(def, value) ? null : def.serialize(value);
52+
} else {
53+
defValue = def.serialize(value);
54+
}
55+
56+
result[def.queryKey] = defValue;
3657
});
3758

3859
return result;

0 commit comments

Comments
 (0)