Skip to content

Commit da72932

Browse files
committed
feat(core): Added debounceTime option for params
fixes #4
1 parent 9cf2a08 commit da72932

6 files changed

Lines changed: 99 additions & 33 deletions

File tree

projects/ngqp-demo/src/app/playground/playground.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export class PlaygroundComponent {
1111
constructor(private queryParamBuilder: QueryParamBuilder) {
1212
this.paramGroup = queryParamBuilder.group({
1313
searchText: queryParamBuilder.param({
14-
name: 'q'
14+
name: 'q',
15+
debounceTime: 1000,
1516
}),
1617
});
1718
}
Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1-
import { Observable, Subject } from 'rxjs';
21
import { isOptionalFunction, wrapTryCatch } from './util';
32

3+
/** TODO Documentation */
4+
export type ParamSerializer<T> = (model: T | null) => string | null;
5+
6+
/** TODO Documentation */
7+
export type ParamDeserializer<T> = (value: string | null) => T | null;
8+
9+
/**
10+
* TODO Documentation
11+
*/
12+
export interface QueryParamControlOpts<T> {
13+
name: string;
14+
serialize?: ParamSerializer<T>;
15+
deserialize?: ParamDeserializer<T>;
16+
debounceTime?: number;
17+
}
18+
419
/**
520
* TODO Documentation
621
*/
@@ -24,25 +39,19 @@ export class QueryParamControl<T> {
2439
public name: string | null = null;
2540

2641
/** TODO Documentation */
27-
public serialize: (model: T) => string;
42+
public serialize: ParamSerializer<T>;
2843

2944
/** TODO Documentation */
30-
public deserialize: (value: string) => T;
45+
public deserialize: ParamDeserializer<T>;
3146

32-
// TODO Who completes this?
33-
private _valueChanges$ = new Subject<T>();
3447
/** TODO Documentation */
35-
public readonly valueChanges$ = this._valueChanges$.asObservable();
48+
public debounceTime: number | null = null;
3649

3750
/** TODO Documentation */
3851
public value: T = null;
3952

4053
constructor(config: QueryParamControlOpts<T>) {
41-
const {
42-
name = null,
43-
serialize = (model: any) => '' + model,
44-
deserialize = (value: string) => value as any,
45-
} = config;
54+
const { name, serialize, deserialize, debounceTime } = config;
4655

4756
if (!isOptionalFunction(serialize)) {
4857
throw new Error(`serialize must be a function, but received ${serialize}`);
@@ -53,18 +62,9 @@ export class QueryParamControl<T> {
5362
}
5463

5564
this.name = name;
56-
5765
this.serialize = wrapTryCatch(serialize, `Error while serializing value for ${name || 'control'}`);
5866
this.deserialize = wrapTryCatch(deserialize, `Error while deserializing value for ${name || 'control'}`);
67+
this.debounceTime = debounceTime;
5968
}
6069

61-
}
62-
63-
/**
64-
* TODO Documentation
65-
*/
66-
export interface QueryParamControlOpts<T> {
67-
name?: string;
68-
serialize?: (model: T) => string | null;
69-
deserialize?: (value: string | null) => T;
7070
}

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { Injectable } from '@angular/core';
22
import { QueryParamControl, QueryParamControlOpts, QueryParamGroup } from './model';
3+
import {
4+
DEFAULT_NUMBER_DESERIALIZER,
5+
DEFAULT_NUMBER_SERIALIZER,
6+
DEFAULT_STRING_DESERIALIZER,
7+
DEFAULT_STRING_SERIALIZER
8+
} from './serializers';
39

410
/**
511
* TODO Documentation
@@ -15,7 +21,7 @@ export class QueryParamBuilder {
1521
public group(config: { [ name: string ]: QueryParamControl<any> | string }): QueryParamGroup {
1622
const controls: { [ controlName: string ]: QueryParamControl<any> } = {};
1723
Object.keys(config).forEach(controlName => {
18-
controls[ controlName ] = this.createControl(config[ controlName ]);
24+
controls[ controlName ] = this.createControl(controlName, config[ controlName ]);
1925
});
2026

2127
return new QueryParamGroup(controls);
@@ -24,16 +30,24 @@ export class QueryParamBuilder {
2430
/**
2531
* TODO Documentation
2632
*/
27-
public param<T>(config: QueryParamControlOpts<T> = {}): QueryParamControl<T> {
28-
return new QueryParamControl(config);
33+
public param(config: QueryParamControlOpts<string>): QueryParamControl<string> {
34+
return new QueryParamControl({
35+
serialize: DEFAULT_STRING_SERIALIZER,
36+
deserialize: DEFAULT_STRING_DESERIALIZER,
37+
...config,
38+
});
2939
}
3040

31-
private createControl<T>(controlConfig: QueryParamControl<T> | string): QueryParamControl<T> {
41+
private createControl<T>(controlName: string, controlConfig: QueryParamControl<T> | string): QueryParamControl<T | string> {
3242
if (controlConfig instanceof QueryParamControl) {
3343
return controlConfig;
3444
}
3545

36-
return this.param();
46+
return this.param({
47+
name: controlName,
48+
serialize: DEFAULT_STRING_SERIALIZER,
49+
deserialize: DEFAULT_STRING_DESERIALIZER,
50+
});
3751
}
3852

3953
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Directive, Input, OnDestroy } from '@angular/core';
22
import { ActivatedRoute, Params, Router } from '@angular/router';
33
import { Subject } from 'rxjs';
4-
import { concatMap, distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
5-
import { QueryParamControl, QueryParamGroup } from './model';
4+
import { concatMap, debounceTime, distinctUntilChanged, map, takeUntil, tap } from 'rxjs/operators';
65
import { QueryParamNameDirective } from './query-param-name.directive';
6+
import { QueryParamControl, QueryParamGroup } from './model';
7+
import { isMissing } from './util';
78

89
/**
910
* TODO Documentation
@@ -49,9 +50,17 @@ export class QueryParamGroupDirective implements OnDestroy {
4950
// to the control itself otherwise.
5051
const paramName = control.name || directive.name;
5152

53+
const paramQueue$ = new Subject<Params>();
54+
paramQueue$
55+
.pipe(
56+
!isMissing(control.debounceTime) ? debounceTime(control.debounceTime) : tap(),
57+
takeUntil(this.destroy$),
58+
)
59+
.subscribe(params => this.enqueueNavigation(params));
60+
5261
// View -> Model
5362
directive.valueAccessor.registerOnChange((newModel: any) => {
54-
this.enqueueNavigation({
63+
paramQueue$.next({
5564
[paramName]: control.serialize(newModel)
5665
});
5766
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { ParamDeserializer, ParamSerializer } from './model';
2+
import { isMissing } from './util';
3+
4+
/**
5+
* TODO Documentation
6+
*/
7+
export function createStringSerializer(defaultValue: string | null = null): ParamSerializer<string> {
8+
return (model: string | null) => isMissing(model) ? defaultValue : model;
9+
}
10+
11+
/**
12+
* TODO Documentation
13+
*/
14+
export function createStringDeserializer(defaultValue: string | null = null): ParamDeserializer<string> {
15+
return (value: string | null) => isMissing(value) ? defaultValue : value;
16+
}
17+
18+
/**
19+
* TODO Documentation
20+
*/
21+
export function createNumberSerializer(defaultValue: string | null = null): ParamSerializer<number> {
22+
return (model: number | null) => isMissing(model) ? defaultValue : `${model}`;
23+
}
24+
25+
/**
26+
* TODO Documentation
27+
*/
28+
export function createNumberDeserializer(defaultValue: number | null = null): ParamDeserializer<number> {
29+
return (value: string | null) => isMissing(value) ? defaultValue : parseFloat(value);
30+
}
31+
32+
/** TODO Documentation */
33+
export const DEFAULT_STRING_SERIALIZER = createStringSerializer();
34+
35+
/** TODO Documentation */
36+
export const DEFAULT_STRING_DESERIALIZER = createStringDeserializer();
37+
38+
/** TODO Documentation */
39+
export const DEFAULT_NUMBER_SERIALIZER = createNumberSerializer();
40+
41+
/** TODO Documentation */
42+
export const DEFAULT_NUMBER_DESERIALIZER = createNumberDeserializer();

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
/**
22
* TODO Documentation
33
*/
4-
export function isDefined(obj: any): boolean {
5-
return obj !== undefined && obj !== null;
4+
export function isMissing(obj: any): obj is null | undefined {
5+
return obj === undefined || obj === null;
66
}
77

88
/**
99
* TODO Documentation
1010
*/
1111
export function isOptionalFunction(obj: any): boolean {
12-
return !isDefined(obj) || typeof obj === 'function';
12+
return isMissing(obj) || typeof obj === 'function';
1313
}
1414

1515
/**

0 commit comments

Comments
 (0)