Skip to content

Commit 0e28c35

Browse files
committed
fix(core): Improve typing and support emptyOn for multi:true
This change improves the type safety by not misinterpreting an array-valued parameter as a multi: true parameter. As a side effect we can now support emptyOn for multi: true parameters. fixes #92 fixes #27 Signed-off-by: Ingo Bürk <ingo.buerk@tngtech.com>
1 parent 78ac98b commit 0e28c35

20 files changed

Lines changed: 247 additions & 246 deletions

projects/ngqp-demo/src/app/docs-items/configuration/query-param/query-param-configuration-docs.component.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ <h2>Components with multiple values</h2>
2929
<span apiDocsLink="QueryParam#multi">multi</span> option to <code>true</code>. This will use the default
3030
behavior of the router to have query parameters with multiple values.
3131
</p>
32+
<div class="alert alert-info">
33+
When using <code>multi: true</code>, APIs such as <span apiDocsLink>QueryParamGroup#get</span> will actually
34+
return <span apiDocsLink>MultiQueryParam</span> instead of <span apiDocsLink>QueryParam</span>.
35+
</div>
3236
<demo-multi-example></demo-multi-example>
3337

3438
<docs-fragment fragment="debounceTime">
@@ -54,9 +58,6 @@ <h2>Debouncing user input</h2>
5458
<docs-fragment fragment="emptyOn-compareWith">
5559
<h2>Default values</h2>
5660
</docs-fragment>
57-
<div class="alert alert-warning">
58-
This feature is currently not supported if you are using <span apiDocsLink>QueryParam#multi</span>.
59-
</div>
6061
<p>
6162
By default, if a parameter value is <code>null</code>, the parameter will be removed from the URL. For example,
6263
this prevents useless URL segments such as <code>?q=</code> (without a value). However, in some cases you might

projects/ngqp-demo/src/app/docs-items/examples/manual-wiring-example/manual-wiring-example.component.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,10 @@ export class ManualWiringExampleComponent implements OnDestroy {
2929
this.pageParam.valueChanges.pipe(
3030
takeUntil(this.componentDestroyed$)
3131
).subscribe(page => this.currentPage = page);
32-
3332
}
3433

3534
public get pageParam(): QueryParam<number> {
36-
return this.paramGroup.get('page');
35+
return this.paramGroup.get('page') as QueryParam<number>;
3736
}
3837

3938
public onPageChange(page: number) {

projects/ngqp-demo/src/app/docs-items/programmatic-access/query-param-group/query-param-group-programmatic-access-docs.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ <h2>Accessing parameters</h2>
55
<p>
66
To get access to a specific <span apiDocsLink>QueryParam</span> use the
77
<span apiDocsLink>QueryParamGroup#get</span> method by passing the name of the parameter to it. This will
8-
return the appropriate <span apiDocsLink>QueryParam</span> instance.
8+
return the appropriate <span apiDocsLink>QueryParam</span> or <span apiDocsLink>MultiQueryParam</span> instance.
99
</p>
1010
<div class="alert alert-info">
1111
Remember that this refers to the name with which the parameter is associated in its group, not the URL parameter

projects/ngqp-demo/src/app/docs-items/programmatic-access/query-param-group/snippets/qpg-api-get.example.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class ExampleComponent {
1111
searchText: qpb.stringParam('q'),
1212
});
1313

14-
const searchText: QueryParam<string> = this.paramGroup.get('searchText');
14+
const searchText: QueryParam<string> = this.paramGroup.get('searchText') as QueryParam<string>;
1515
}
1616

1717
}

projects/ngqp-demo/src/app/shared/docs-page.pipes.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ export class DocsPageNamePipe implements PipeTransform {
1616
case DocsPage.GETTING_HELP:
1717
return 'Getting Help';
1818
case DocsPage.CONFIGURATION_QUERYPARAMMODULE:
19-
return 'QueryParamModule';
19+
return 'Global';
2020
case DocsPage.CONFIGURATION_QUERYPARAMGROUP:
21-
return 'QueryParamGroup';
21+
return 'Groups';
2222
case DocsPage.CONFIGURATION_QUERYPARAM:
23-
return 'QueryParam';
23+
return 'Parameters';
2424
case DocsPage.PROGRAMMATIC_QUERYPARAMGROUP:
25-
return 'QueryParamGroup';
25+
return 'Groups';
2626
case DocsPage.PROGRAMMATIC_QUERYPARAM:
27-
return 'QueryParam';
27+
return 'Parameters';
2828
case DocsPage.CUSTOM_CONTROL_VALUE_ACCESSOR:
2929
return 'Controls without ControlValueAccessor';
3030
default:

projects/ngqp/core/src/lib/core.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ export { QueryParamBuilder } from './query-param-builder.service';
22
export { QueryParamModule } from './query-param.module';
33

44
export {
5-
createEmptyOnSerializer,
6-
createEmptyOnDeserializer,
75
createStringSerializer,
86
createStringDeserializer,
97
createNumberSerializer,

projects/ngqp/core/src/lib/directives/query-param-group.service.ts

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,16 @@ import {
1414
tap
1515
} from 'rxjs/operators';
1616
import { compareParamMaps, filterParamMap, isMissing, isPresent, NOP } from '../util';
17-
import { Unpack } from '../types';
1817
import { QueryParamGroup } from '../model/query-param-group';
19-
import { QueryParam } from '../model/query-param';
18+
import { MultiQueryParam, QueryParam } from '../model/query-param';
2019
import { NGQP_ROUTER_ADAPTER, NGQP_ROUTER_OPTIONS, RouterAdapter, RouterOptions } from '../router-adapter/router-adapter.interface';
2120
import { QueryParamAccessor } from './query-param-accessor.interface';
2221

2322
/** @internal */
24-
function isMultiQueryParam<T>(queryParam: QueryParam<T> | QueryParam<T[]>): queryParam is QueryParam<T[]> {
23+
function isMultiQueryParam<T>(queryParam: QueryParam<T> | MultiQueryParam<T>): queryParam is MultiQueryParam<T> {
2524
return queryParam.multi;
2625
}
2726

28-
/** @internal */
29-
function hasArrayValue<T>(queryParam: QueryParam<T> | QueryParam<T[]>, value: T | T[]): value is T[] {
30-
return isMultiQueryParam(queryParam);
31-
}
32-
33-
/** @internal */
34-
function hasArraySerialization(queryParam: QueryParam<any>, values: string | string[] | null): values is string[] {
35-
return isMultiQueryParam(queryParam);
36-
}
37-
3827
/** @internal */
3928
class NavigationData {
4029
constructor(public params: Params, public synthetic: boolean = false) {
@@ -113,7 +102,7 @@ export class QueryParamGroupService implements OnDestroy {
113102
// it as it might change over time.
114103
const queryParamName = directive.name;
115104

116-
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
105+
const queryParam = this.queryParamGroup.get(queryParamName);
117106
if (!queryParam) {
118107
throw new Error(`Could not find query param with name ${queryParamName}. Did you forget to add it to your QueryParamGroup?`);
119108
}
@@ -160,7 +149,7 @@ export class QueryParamGroupService implements OnDestroy {
160149
});
161150

162151
this.directives.delete(queryParamName);
163-
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
152+
const queryParam = this.queryParamGroup.get(queryParamName);
164153
if (queryParam) {
165154
queryParam._clearChangeFunctions();
166155
}
@@ -179,7 +168,7 @@ export class QueryParamGroupService implements OnDestroy {
179168
this.queryParamGroup._registerOnChange((newValue: Record<string, any>) => {
180169
let params: Params = {};
181170
Object.keys(newValue).forEach(queryParamName => {
182-
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
171+
const queryParam = this.queryParamGroup.get(queryParamName);
183172
if (isMissing(queryParam)) {
184173
return;
185174
}
@@ -198,7 +187,7 @@ export class QueryParamGroupService implements OnDestroy {
198187
}
199188

200189
private setupParamChangeListener(queryParamName: string): void {
201-
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
190+
const queryParam = this.queryParamGroup.get(queryParamName);
202191
if (!queryParam) {
203192
throw new Error(`No param in group found for name ${queryParamName}`);
204193
}
@@ -232,10 +221,10 @@ export class QueryParamGroupService implements OnDestroy {
232221
const groupValue: Record<string, any> = {};
233222

234223
Object.keys(this.queryParamGroup.queryParams).forEach(queryParamName => {
235-
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
236-
const newValue = queryParam.multi
237-
? this.deserialize(queryParam, queryParamMap.getAll(queryParam.urlParam))
238-
: this.deserialize(queryParam, queryParamMap.get(queryParam.urlParam));
224+
const queryParam = this.queryParamGroup.get(queryParamName);
225+
const newValue = isMultiQueryParam(queryParam)
226+
? queryParam.deserializeValue(queryParamMap.getAll(queryParam.urlParam))
227+
: queryParam.deserializeValue(queryParamMap.get(queryParam.urlParam));
239228

240229
const directives = this.directives.get(queryParamName);
241230
if (directives) {
@@ -310,8 +299,8 @@ export class QueryParamGroupService implements OnDestroy {
310299
* This consists mainly of properly serializing the model value and ensuring to take
311300
* side effect changes into account that may have been configured.
312301
*/
313-
private getParamsForValue<T>(queryParam: QueryParam<any>, value: T | undefined | null): Params {
314-
const newValue = this.serialize(queryParam, value);
302+
private getParamsForValue(queryParam: QueryParam<any> | MultiQueryParam<any>, value: any | undefined | null): Params {
303+
const newValue = queryParam.serializeValue(value);
315304

316305
const combinedParams: Params = isMissing(queryParam.combineWith)
317306
? {} : queryParam.combineWith(value);
@@ -324,22 +313,6 @@ export class QueryParamGroupService implements OnDestroy {
324313
};
325314
}
326315

327-
private serialize<T>(queryParam: QueryParam<any>, value: T): string | string[] {
328-
if (hasArrayValue(queryParam, value)) {
329-
return (value || []).map(queryParam.serialize);
330-
} else {
331-
return queryParam.serialize(value);
332-
}
333-
}
334-
335-
private deserialize<T>(queryParam: QueryParam<T>, values: string | string[]): Unpack<T> | Unpack<T>[] {
336-
if (hasArraySerialization(queryParam, values)) {
337-
return values.map(queryParam.deserialize);
338-
} else {
339-
return queryParam.deserialize(values);
340-
}
341-
}
342-
343316
/**
344317
* Returns the current set of options to pass to the router.
345318
*
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export { QueryParamOpts } from './query-param-opts';
1+
export { QueryParamOpts, MultiQueryParamOpts } from './query-param-opts';
22
export { QueryParamGroup } from './query-param-group';
3-
export { QueryParam } from './query-param';
3+
export { AbstractQueryParam, QueryParam, MultiQueryParam } from './query-param';

projects/ngqp/core/src/lib/model/query-param-group.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe(QueryParamGroup.name, () => {
4646
let group: QueryParamGroup;
4747
let queryParam: QueryParam<string>;
4848
beforeEach(() => group = new QueryParamGroup({ q: stringParam }));
49-
beforeEach(() => queryParam = group.get('q'));
49+
beforeEach(() => queryParam = group.get('q') as QueryParam<string> );
5050

5151
it('updates the parameter value', () => {
5252
group.patchValue({ q: 'Test' });
@@ -114,7 +114,7 @@ describe(QueryParamGroup.name, () => {
114114
let group: QueryParamGroup;
115115
let queryParam: QueryParam<string>;
116116
beforeEach(() => group = new QueryParamGroup({ q: stringParam }));
117-
beforeEach(() => queryParam = group.get('q'));
117+
beforeEach(() => queryParam = group.get('q') as QueryParam<string>);
118118

119119
it('updates the parameter value', () => {
120120
group.setValue({ q: 'Test' });
@@ -178,7 +178,7 @@ describe(QueryParamGroup.name, () => {
178178
let group: QueryParamGroup;
179179
let queryParam: QueryParam<string>;
180180
beforeEach(() => group = new QueryParamGroup({ q: stringParam }));
181-
beforeEach(() => queryParam = group.get('q'));
181+
beforeEach(() => queryParam = group.get('q') as QueryParam<string>);
182182

183183
it('emits the current value', fakeAsync(() => {
184184
scheduler.run(({ expectObservable }) => {

projects/ngqp/core/src/lib/model/query-param-group.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Observable, Subject } from 'rxjs';
22
import { isMissing, undefinedToNull } from '../util';
33
import { OnChangeFunction } from '../types';
4-
import { QueryParam } from './query-param';
4+
import { MultiQueryParam, QueryParam } from './query-param';
55
import { RouterOptions } from '../router-adapter/router-adapter.interface';
66

77
/**
@@ -34,15 +34,15 @@ export class QueryParamGroup {
3434
public readonly queryParamAdded$: Observable<string> = this._queryParamAdded$.asObservable();
3535

3636
/** @internal */
37-
public readonly queryParams: { [ queryParamName: string ]: QueryParam<any> };
37+
public readonly queryParams: { [ queryParamName: string ]: QueryParam<any> | MultiQueryParam<any> };
3838

3939
/** @internal */
4040
public readonly routerOptions: RouterOptions;
4141

4242
private changeFunctions: OnChangeFunction<Record<string, any>>[] = [];
4343

4444
constructor(
45-
queryParams: { [ queryParamName: string ]: QueryParam<any> },
45+
queryParams: { [ queryParamName: string ]: QueryParam<any> | MultiQueryParam<any> },
4646
extras: RouterOptions = {}
4747
) {
4848
this.queryParams = queryParams;
@@ -69,7 +69,7 @@ export class QueryParamGroup {
6969
*
7070
* @param queryParamName The name of the parameter instance to retrieve.
7171
*/
72-
public get(queryParamName: string): QueryParam<any> | null {
72+
public get(queryParamName: string): QueryParam<any> | MultiQueryParam<any> | null {
7373
const param = this.queryParams[ queryParamName ];
7474
if (!param) {
7575
return null;
@@ -88,7 +88,7 @@ export class QueryParamGroup {
8888
* @param queryParamName Name of the parameter to reference it with.
8989
* @param queryParam The new parameter to add.
9090
*/
91-
public add(queryParamName: string, queryParam: QueryParam<any>): void {
91+
public add(queryParamName: string, queryParam: QueryParam<any> | MultiQueryParam<any>): void {
9292
if (this.get(queryParamName)) {
9393
throw new Error(`A parameter with name ${queryParamName} already exists.`);
9494
}
@@ -107,7 +107,7 @@ export class QueryParamGroup {
107107
* @param queryParamName The name of the parameter to remove.
108108
*/
109109
public remove(queryParamName: string): void {
110-
const queryParam: QueryParam<any> = this.get(queryParamName);
110+
const queryParam = this.get(queryParamName);
111111
if (!queryParam) {
112112
throw new Error(`No parameter with name ${queryParamName} found.`);
113113
}

0 commit comments

Comments
 (0)