@ethlete/query 5.16.0 → 5.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/esm2022/ethlete-query.mjs +5 -0
- package/esm2022/index.mjs +20 -0
- package/esm2022/lib/auth/auth-provider.types.mjs +2 -0
- package/esm2022/lib/auth/auth-provider.utils.mjs +25 -0
- package/esm2022/lib/auth/basic-auth-provider.mjs +15 -0
- package/esm2022/lib/auth/bearer-auth-provider.mjs +168 -0
- package/esm2022/lib/auth/custom-header-auth-provider.mjs +12 -0
- package/esm2022/lib/auth/index.mjs +6 -0
- package/esm2022/lib/class-validator/class-validator.types.mjs +2 -0
- package/esm2022/lib/class-validator/class-validator.utils.mjs +4 -0
- package/esm2022/lib/class-validator/index.mjs +3 -0
- package/esm2022/lib/devtools/components/query-devtools/index.mjs +2 -0
- package/esm2022/lib/devtools/components/query-devtools/query-devtools.component.mjs +297 -0
- package/esm2022/lib/devtools/pipes/index.mjs +2 -0
- package/esm2022/lib/devtools/pipes/query-short-name.pipe.mjs +24 -0
- package/esm2022/lib/devtools/utils/index.mjs +2 -0
- package/esm2022/lib/devtools/utils/query-client.utils.mjs +13 -0
- package/esm2022/lib/directives/index.mjs +4 -0
- package/esm2022/lib/directives/infinity-query-trigger.directive.mjs +56 -0
- package/esm2022/lib/directives/infinity-query.directive.mjs +147 -0
- package/esm2022/lib/directives/query.directive.mjs +123 -0
- package/esm2022/lib/entity/entity-store.mjs +127 -0
- package/esm2022/lib/entity/entity.types.mjs +2 -0
- package/esm2022/lib/entity/entity.utils.mjs +29 -0
- package/esm2022/lib/entity/index.mjs +4 -0
- package/esm2022/lib/gql/gql.mjs +23 -0
- package/esm2022/lib/gql/index.mjs +2 -0
- package/esm2022/lib/infinite-query/index.mjs +4 -0
- package/esm2022/lib/infinite-query/infinity-query.mjs +171 -0
- package/esm2022/lib/infinite-query/infinity-query.types.mjs +2 -0
- package/esm2022/lib/infinite-query/infinity-query.utils.mjs +3 -0
- package/esm2022/lib/logger/index.mjs +2 -0
- package/esm2022/lib/logger/logger.mjs +16 -0
- package/esm2022/lib/query/index.mjs +4 -0
- package/esm2022/lib/query/query.mjs +428 -0
- package/esm2022/lib/query/query.types.mjs +2 -0
- package/esm2022/lib/query/query.utils.mjs +253 -0
- package/esm2022/lib/query-client/index.mjs +4 -0
- package/esm2022/lib/query-client/query-client.mjs +80 -0
- package/esm2022/lib/query-client/query-client.types.mjs +2 -0
- package/esm2022/lib/query-client/query-client.utils.mjs +20 -0
- package/esm2022/lib/query-creator/index.mjs +3 -0
- package/esm2022/lib/query-creator/query-creator.mjs +55 -0
- package/esm2022/lib/query-creator/query-creator.types.mjs +2 -0
- package/esm2022/lib/query-form/index.mjs +4 -0
- package/esm2022/lib/query-form/query-form.mjs +379 -0
- package/esm2022/lib/query-form/query-form.types.mjs +2 -0
- package/esm2022/lib/query-form/query-form.utils.mjs +97 -0
- package/esm2022/lib/query-store/index.mjs +2 -0
- package/esm2022/lib/query-store/query-store.mjs +144 -0
- package/esm2022/lib/request/index.mjs +4 -0
- package/esm2022/lib/request/request.mjs +196 -0
- package/esm2022/lib/request/request.types.mjs +2 -0
- package/esm2022/lib/request/request.util.mjs +252 -0
- package/esm2022/lib/symfony/index.mjs +3 -0
- package/esm2022/lib/symfony/symfony.types.mjs +2 -0
- package/esm2022/lib/symfony/symfony.utils.mjs +31 -0
- package/esm2022/lib/utils/data.utils.mjs +138 -0
- package/esm2022/lib/utils/deep-freeze.util.mjs +17 -0
- package/esm2022/lib/utils/def.util.mjs +8 -0
- package/esm2022/lib/utils/index.mjs +4 -0
- package/fesm2022/ethlete-query.mjs +3287 -0
- package/fesm2022/ethlete-query.mjs.map +1 -0
- package/{src/index.ts → index.d.ts} +0 -2
- package/lib/auth/auth-provider.types.d.ts +133 -0
- package/lib/auth/auth-provider.utils.d.ts +9 -0
- package/lib/auth/basic-auth-provider.d.ts +10 -0
- package/lib/auth/bearer-auth-provider.d.ts +23 -0
- package/lib/auth/custom-header-auth-provider.d.ts +9 -0
- package/{src/lib/class-validator/class-validator.types.ts → lib/class-validator/class-validator.types.d.ts} +3 -4
- package/lib/class-validator/class-validator.utils.d.ts +2 -0
- package/lib/devtools/components/query-devtools/query-devtools.component.d.ts +151 -0
- package/lib/devtools/pipes/query-short-name.pipe.d.ts +8 -0
- package/lib/devtools/utils/query-client.utils.d.ts +12 -0
- package/lib/directives/infinity-query-trigger.directive.d.ts +15 -0
- package/lib/directives/infinity-query.directive.d.ts +49 -0
- package/lib/directives/query.directive.d.ts +63 -0
- package/lib/entity/entity-store.d.ts +25 -0
- package/lib/entity/entity.types.d.ts +25 -0
- package/lib/entity/entity.utils.d.ts +12 -0
- package/lib/gql/gql.d.ts +6 -0
- package/lib/infinite-query/infinity-query.d.ts +29 -0
- package/lib/infinite-query/infinity-query.types.d.ts +104 -0
- package/lib/infinite-query/infinity-query.utils.d.ts +4 -0
- package/lib/logger/logger.d.ts +12 -0
- package/lib/query/query.d.ts +60 -0
- package/lib/query/query.types.d.ts +388 -0
- package/lib/query/query.utils.d.ts +91 -0
- package/lib/query-client/query-client.d.ts +29 -0
- package/lib/query-client/query-client.types.d.ts +82 -0
- package/lib/query-client/query-client.utils.d.ts +4 -0
- package/lib/query-creator/query-creator.d.ts +20 -0
- package/lib/query-creator/query-creator.types.d.ts +13 -0
- package/lib/query-form/query-form.d.ts +91 -0
- package/lib/query-form/query-form.types.d.ts +145 -0
- package/lib/query-form/query-form.utils.d.ts +13 -0
- package/lib/query-store/query-store.d.ts +31 -0
- package/lib/request/request.d.ts +3 -0
- package/lib/request/request.types.d.ts +126 -0
- package/lib/request/request.util.d.ts +92 -0
- package/lib/symfony/symfony.types.d.ts +19 -0
- package/lib/symfony/symfony.utils.d.ts +6 -0
- package/lib/utils/data.utils.d.ts +59 -0
- package/lib/utils/deep-freeze.util.d.ts +1 -0
- package/{src/lib/utils/def.util.ts → lib/utils/def.util.d.ts} +1 -1
- package/package.json +19 -2
- package/.eslintrc.json +0 -40
- package/jest.config.ts +0 -23
- package/ng-package.json +0 -8
- package/project.json +0 -37
- package/src/lib/auth/auth-provider.types.ts +0 -160
- package/src/lib/auth/auth-provider.utils.ts +0 -39
- package/src/lib/auth/basic-auth-provider.ts +0 -17
- package/src/lib/auth/bearer-auth-provider.ts +0 -243
- package/src/lib/auth/custom-header-auth-provider.ts +0 -13
- package/src/lib/class-validator/class-validator.utils.ts +0 -5
- package/src/lib/devtools/components/query-devtools/query-devtools.component.html +0 -402
- package/src/lib/devtools/components/query-devtools/query-devtools.component.scss +0 -360
- package/src/lib/devtools/components/query-devtools/query-devtools.component.ts +0 -427
- package/src/lib/devtools/pipes/query-short-name.pipe.ts +0 -24
- package/src/lib/devtools/utils/query-client.utils.ts +0 -25
- package/src/lib/directives/infinity-query-trigger.directive.ts +0 -58
- package/src/lib/directives/infinity-query.directive.ts +0 -208
- package/src/lib/directives/query.directive.ts +0 -201
- package/src/lib/entity/entity-store.ts +0 -180
- package/src/lib/entity/entity.types.ts +0 -34
- package/src/lib/entity/entity.utils.ts +0 -51
- package/src/lib/gql/gql.ts +0 -22
- package/src/lib/infinite-query/infinity-query.ts +0 -245
- package/src/lib/infinite-query/infinity-query.types.ts +0 -149
- package/src/lib/infinite-query/infinity-query.utils.ts +0 -14
- package/src/lib/logger/logger.ts +0 -30
- package/src/lib/query/query.ts +0 -631
- package/src/lib/query/query.types.ts +0 -532
- package/src/lib/query/query.utils.spec.ts +0 -92
- package/src/lib/query/query.utils.ts +0 -450
- package/src/lib/query-client/query-client.spec.ts +0 -77
- package/src/lib/query-client/query-client.ts +0 -195
- package/src/lib/query-client/query-client.types.ts +0 -101
- package/src/lib/query-client/query-client.utils.spec.ts +0 -61
- package/src/lib/query-client/query-client.utils.ts +0 -28
- package/src/lib/query-creator/query-creator.ts +0 -103
- package/src/lib/query-creator/query-creator.types.ts +0 -74
- package/src/lib/query-form/query-form.ts +0 -526
- package/src/lib/query-form/query-form.types.ts +0 -169
- package/src/lib/query-form/query-form.utils.ts +0 -116
- package/src/lib/query-store/query-store.ts +0 -192
- package/src/lib/request/request.ts +0 -253
- package/src/lib/request/request.types.ts +0 -164
- package/src/lib/request/request.util.spec.ts +0 -177
- package/src/lib/request/request.util.ts +0 -399
- package/src/lib/stories/angular-example-page.mdx +0 -85
- package/src/lib/stories/authentication-page.mdx +0 -35
- package/src/lib/stories/changelog-page.mdx +0 -9
- package/src/lib/stories/client-page.mdx +0 -100
- package/src/lib/stories/queries-page.mdx +0 -117
- package/src/lib/symfony/symfony.types.ts +0 -21
- package/src/lib/symfony/symfony.utils.ts +0 -43
- package/src/lib/utils/data.utils.ts +0 -280
- package/src/lib/utils/deep-freeze.util.ts +0 -21
- package/src/test-setup.ts +0 -1
- package/tsconfig.json +0 -29
- package/tsconfig.lib.json +0 -12
- package/tsconfig.lib.prod.json +0 -10
- package/tsconfig.spec.json +0 -11
- /package/{src/lib/auth/index.ts → lib/auth/index.d.ts} +0 -0
- /package/{src/lib/class-validator/index.ts → lib/class-validator/index.d.ts} +0 -0
- /package/{src/lib/devtools/components/query-devtools/index.ts → lib/devtools/components/query-devtools/index.d.ts} +0 -0
- /package/{src/lib/devtools/pipes/index.ts → lib/devtools/pipes/index.d.ts} +0 -0
- /package/{src/lib/devtools/utils/index.ts → lib/devtools/utils/index.d.ts} +0 -0
- /package/{src/lib/directives/index.ts → lib/directives/index.d.ts} +0 -0
- /package/{src/lib/entity/index.ts → lib/entity/index.d.ts} +0 -0
- /package/{src/lib/gql/index.ts → lib/gql/index.d.ts} +0 -0
- /package/{src/lib/infinite-query/index.ts → lib/infinite-query/index.d.ts} +0 -0
- /package/{src/lib/logger/index.ts → lib/logger/index.d.ts} +0 -0
- /package/{src/lib/query/index.ts → lib/query/index.d.ts} +0 -0
- /package/{src/lib/query-client/index.ts → lib/query-client/index.d.ts} +0 -0
- /package/{src/lib/query-creator/index.ts → lib/query-creator/index.d.ts} +0 -0
- /package/{src/lib/query-form/index.ts → lib/query-form/index.d.ts} +0 -0
- /package/{src/lib/query-store/index.ts → lib/query-store/index.d.ts} +0 -0
- /package/{src/lib/request/index.ts → lib/request/index.d.ts} +0 -0
- /package/{src/lib/symfony/index.ts → lib/symfony/index.d.ts} +0 -0
- /package/{src/lib/utils/index.ts → lib/utils/index.d.ts} +0 -0
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
import { NgZone, assertInInjectionContext, inject, isDevMode } from '@angular/core';
|
|
2
|
-
import { toSignal } from '@angular/core/rxjs-interop';
|
|
3
|
-
import { FormGroup } from '@angular/forms';
|
|
4
|
-
import { ActivatedRoute, Router } from '@angular/router';
|
|
5
|
-
import { ET_PROPERTY_REMOVED, RouterStateService, clone, createDestroy, equal } from '@ethlete/core';
|
|
6
|
-
import { BehaviorSubject, Subject, debounceTime, map, merge, of, switchMap, takeUntil, tap, timer } from 'rxjs';
|
|
7
|
-
import {
|
|
8
|
-
QueryFieldOptions,
|
|
9
|
-
QueryFormGroup,
|
|
10
|
-
QueryFormGroupControls,
|
|
11
|
-
QueryFormObserveOptions,
|
|
12
|
-
QueryFormValue,
|
|
13
|
-
QueryFormValueEvent,
|
|
14
|
-
QueryFormWriteOptions,
|
|
15
|
-
} from './query-form.types';
|
|
16
|
-
import { transformToBoolean, transformToNumber } from './query-form.utils';
|
|
17
|
-
|
|
18
|
-
const ET_ARR_PREFIX = 'ET_ARR__';
|
|
19
|
-
|
|
20
|
-
export class QueryField<T> {
|
|
21
|
-
get control() {
|
|
22
|
-
return this.data.control;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
constructor(public data: QueryFieldOptions<T>) {}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const IGNORED_FILTER_COUNT_FIELDS = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
|
|
29
|
-
|
|
30
|
-
export interface QueryFormOptions {
|
|
31
|
-
/**
|
|
32
|
-
* A prefix to use for the query parameters. This is useful when you have multiple query forms on the same page.
|
|
33
|
-
*/
|
|
34
|
-
queryParamPrefix?: string | (() => string);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
-
export type AnyQueryForm = QueryForm<any>;
|
|
39
|
-
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
-
export class QueryForm<T extends Record<string, QueryField<any>>> {
|
|
42
|
-
private readonly _destroy$ = createDestroy();
|
|
43
|
-
private readonly _unobserveTrigger$ = new Subject<boolean>();
|
|
44
|
-
private readonly _router = inject(Router);
|
|
45
|
-
private readonly _activatedRoute = inject(ActivatedRoute);
|
|
46
|
-
private readonly _defaultValues = this._extractDefaultValues();
|
|
47
|
-
private readonly _routerStateService = inject(RouterStateService);
|
|
48
|
-
private readonly _zone = inject(NgZone);
|
|
49
|
-
private readonly _didValueChanges$ = new Subject<boolean>();
|
|
50
|
-
|
|
51
|
-
private _isObserving = false;
|
|
52
|
-
private _skipNextResets = false;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The angular form group that contains all the fields.
|
|
56
|
-
*
|
|
57
|
-
* **Do not** use any of the following methods on this form group:
|
|
58
|
-
* - `setValue`: Use `QueryForm.setValue` instead.
|
|
59
|
-
* - `patchValue`: Use `QueryForm.patchValue` instead.
|
|
60
|
-
* - `valueChanges`: Use `QueryForm.changes$` instead.
|
|
61
|
-
* - `value`: Use `QueryForm.value` instead.
|
|
62
|
-
* - `controls`: Use `QueryForm.controls` instead.
|
|
63
|
-
*/
|
|
64
|
-
readonly form = this._setupFormGroup();
|
|
65
|
-
|
|
66
|
-
private readonly _lastFormValue$ = new BehaviorSubject<QueryFormValue<T> | null>(null);
|
|
67
|
-
private readonly _currentFormValue$ = new BehaviorSubject<QueryFormValue<T>>(this._formValue);
|
|
68
|
-
|
|
69
|
-
private get _formValue() {
|
|
70
|
-
return this.form.getRawValue() as QueryFormValue<T>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private get _form() {
|
|
74
|
-
return this.form as unknown as QueryFormGroup;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private readonly _changes$ = new BehaviorSubject<QueryFormValueEvent<T>>({
|
|
78
|
-
previousValue: null,
|
|
79
|
-
currentValue: this.form.getRawValue() as QueryFormValue<T>,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
readonly changes$ = this._changes$.asObservable();
|
|
83
|
-
readonly currentValue$ = this._changes$.pipe(map(({ currentValue }) => currentValue));
|
|
84
|
-
readonly previousValue$ = this._changes$.pipe(map(({ previousValue }) => previousValue));
|
|
85
|
-
|
|
86
|
-
readonly changes = toSignal(this.changes$);
|
|
87
|
-
readonly currentValue = toSignal(this.currentValue$);
|
|
88
|
-
readonly previousValue = toSignal(this.previousValue$);
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* The number of active filters.
|
|
92
|
-
*
|
|
93
|
-
* Excludes the following fields by default:
|
|
94
|
-
* - `page`
|
|
95
|
-
* - `skip`
|
|
96
|
-
* - `take`
|
|
97
|
-
* - `limit`
|
|
98
|
-
* - `sort`
|
|
99
|
-
* - `sortBy`
|
|
100
|
-
* - `sortOrder`
|
|
101
|
-
* - `query`
|
|
102
|
-
* - `search`
|
|
103
|
-
*/
|
|
104
|
-
readonly activeFilterCount$ = this.changes$.pipe(
|
|
105
|
-
map(
|
|
106
|
-
({ currentValue }) =>
|
|
107
|
-
Object.entries(currentValue)
|
|
108
|
-
.map(([key, value]) => {
|
|
109
|
-
if (IGNORED_FILTER_COUNT_FIELDS.includes(key) || this._fields[key]?.data.skipInFilterCount) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return this._isDefaultValue(key, value);
|
|
114
|
-
})
|
|
115
|
-
.filter((v) => v === false).length,
|
|
116
|
-
),
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
get controls() {
|
|
120
|
-
return this.form.controls;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
get value() {
|
|
124
|
-
return this._changes$.value.currentValue;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
get defaultFormValue() {
|
|
128
|
-
return Object.entries(this._defaultValues).reduce((acc, [key]) => {
|
|
129
|
-
acc[key as keyof QueryFormValue<T>] = this._getDefaultValue(key);
|
|
130
|
-
|
|
131
|
-
return acc;
|
|
132
|
-
}, {} as QueryFormValue<T>);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// with prefix eg. page should become ${prefix}-page
|
|
136
|
-
constructor(
|
|
137
|
-
private _fields: T,
|
|
138
|
-
private _options?: QueryFormOptions,
|
|
139
|
-
) {
|
|
140
|
-
assertInInjectionContext(QueryForm);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
observe(options?: QueryFormObserveOptions) {
|
|
144
|
-
if (this._isObserving) {
|
|
145
|
-
if (isDevMode()) {
|
|
146
|
-
console.warn('QueryForm.observe() was called multiple times. This is not supported.');
|
|
147
|
-
}
|
|
148
|
-
return this;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
this._isObserving = true;
|
|
152
|
-
|
|
153
|
-
if (options?.syncOnNavigation !== false) {
|
|
154
|
-
const didChanges = this.setFormValueFromUrlQueryParams({
|
|
155
|
-
queryParams: this._routerStateService.queryParams,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
if (didChanges) {
|
|
159
|
-
this._handleFormChange(true);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
merge(...Object.values(this._fields).map((field) => field.control.valueChanges), this._didValueChanges$)
|
|
164
|
-
.pipe(
|
|
165
|
-
debounceTime(0),
|
|
166
|
-
tap(() => {
|
|
167
|
-
this._handleFormChange();
|
|
168
|
-
}),
|
|
169
|
-
takeUntil(this._destroy$),
|
|
170
|
-
takeUntil(this._unobserveTrigger$),
|
|
171
|
-
)
|
|
172
|
-
.subscribe();
|
|
173
|
-
|
|
174
|
-
let changedFieldsInLastResetLoop: string[] = [];
|
|
175
|
-
let currentUniqueChangedFields: string[] = [];
|
|
176
|
-
|
|
177
|
-
this._currentFormValue$
|
|
178
|
-
.pipe(
|
|
179
|
-
map((currentValue) => {
|
|
180
|
-
return {
|
|
181
|
-
previousValue: clone(this._lastFormValue$.value),
|
|
182
|
-
currentValue: clone(currentValue),
|
|
183
|
-
};
|
|
184
|
-
}),
|
|
185
|
-
tap(({ currentValue, previousValue }) => {
|
|
186
|
-
if (options?.writeToQueryParams !== false) {
|
|
187
|
-
this._syncViaUrlQueryParams(currentValue, options?.replaceUrl);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const didResetValues = this._skipNextResets
|
|
191
|
-
? false
|
|
192
|
-
: this._handleQueryFormResets(previousValue ?? null, currentValue);
|
|
193
|
-
|
|
194
|
-
this._skipNextResets = false;
|
|
195
|
-
|
|
196
|
-
const changedFields = Object.keys(currentValue).filter(
|
|
197
|
-
(key) => !equal(previousValue?.[key], currentValue[key]),
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
if (changedFieldsInLastResetLoop.length) {
|
|
201
|
-
changedFields.push(...changedFieldsInLastResetLoop);
|
|
202
|
-
changedFieldsInLastResetLoop = [];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (didResetValues) {
|
|
206
|
-
this._didValueChanges$.next(true);
|
|
207
|
-
changedFieldsInLastResetLoop = changedFields;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
currentUniqueChangedFields = [...new Set(changedFields)];
|
|
211
|
-
}),
|
|
212
|
-
switchMap(({ currentValue, previousValue }) => {
|
|
213
|
-
if (changedFieldsInLastResetLoop.length) return of(null).pipe(map(() => ({ currentValue, previousValue })));
|
|
214
|
-
|
|
215
|
-
const debounceValues = currentUniqueChangedFields.map((key) => {
|
|
216
|
-
const field = this._fields[key];
|
|
217
|
-
|
|
218
|
-
if (!field) return null;
|
|
219
|
-
|
|
220
|
-
if (field.data.disableDebounceIfFalsy === true && !currentValue[key]) {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return field.data.debounce ?? null;
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
currentUniqueChangedFields = [];
|
|
228
|
-
|
|
229
|
-
if (debounceValues.some((v) => v === null) || !debounceValues.length) {
|
|
230
|
-
return of(null).pipe(map(() => ({ currentValue, previousValue })));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const shortestDebounceTime = Math.min(...debounceValues.filter((v): v is number => v !== null));
|
|
234
|
-
|
|
235
|
-
return timer(shortestDebounceTime).pipe(map(() => ({ currentValue, previousValue })));
|
|
236
|
-
}),
|
|
237
|
-
tap(({ currentValue, previousValue }) => {
|
|
238
|
-
if (changedFieldsInLastResetLoop.length) return;
|
|
239
|
-
|
|
240
|
-
this._changes$.next({
|
|
241
|
-
previousValue: previousValue ?? null,
|
|
242
|
-
currentValue: currentValue,
|
|
243
|
-
});
|
|
244
|
-
}),
|
|
245
|
-
takeUntil(this._destroy$),
|
|
246
|
-
takeUntil(this._unobserveTrigger$),
|
|
247
|
-
)
|
|
248
|
-
.subscribe();
|
|
249
|
-
|
|
250
|
-
if (options?.syncOnNavigation !== false) {
|
|
251
|
-
this._routerStateService.queryParamChanges$
|
|
252
|
-
.pipe(
|
|
253
|
-
takeUntil(this._destroy$),
|
|
254
|
-
takeUntil(this._unobserveTrigger$),
|
|
255
|
-
tap((changes) => {
|
|
256
|
-
const didValueChanges = this.setFormValueFromUrlQueryParams({ queryParams: changes });
|
|
257
|
-
|
|
258
|
-
if (didValueChanges) {
|
|
259
|
-
this._didValueChanges$.next(true);
|
|
260
|
-
}
|
|
261
|
-
}),
|
|
262
|
-
)
|
|
263
|
-
.subscribe();
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return this;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private _handleQueryFormResets(previousValue: QueryFormValue<T> | null, currentValue: QueryFormValue<T>) {
|
|
270
|
-
let didResetValues = false;
|
|
271
|
-
|
|
272
|
-
for (const formFieldKey in this._fields) {
|
|
273
|
-
const field = this._fields[formFieldKey];
|
|
274
|
-
|
|
275
|
-
if (!field) continue;
|
|
276
|
-
|
|
277
|
-
const resets = field.data.isResetBy;
|
|
278
|
-
|
|
279
|
-
if (!resets) continue;
|
|
280
|
-
|
|
281
|
-
const resetConditionKeys = Array.isArray(resets) ? resets : [resets];
|
|
282
|
-
|
|
283
|
-
for (const resetConditionKey of resetConditionKeys) {
|
|
284
|
-
if (!(resetConditionKey in this._fields)) {
|
|
285
|
-
if (isDevMode()) {
|
|
286
|
-
console.warn(`The field "${resetConditionKey}" is not defined in the QueryForm. Is it a typo?`, this);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
previousValue &&
|
|
294
|
-
currentValue &&
|
|
295
|
-
!equal(previousValue[resetConditionKey], currentValue[resetConditionKey])
|
|
296
|
-
) {
|
|
297
|
-
const defaultValueForKeyToReset = this._getDefaultValue(formFieldKey);
|
|
298
|
-
const currentValueForKeyToReset = currentValue[formFieldKey];
|
|
299
|
-
|
|
300
|
-
if (equal(defaultValueForKeyToReset, currentValueForKeyToReset)) {
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
this.form.controls[formFieldKey].setValue(defaultValueForKeyToReset);
|
|
305
|
-
|
|
306
|
-
didResetValues = true;
|
|
307
|
-
|
|
308
|
-
break;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return didResetValues;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
unobserve() {
|
|
317
|
-
this._unobserveTrigger$.next(true);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
setFormValueFromUrlQueryParams(options: { queryParams: Record<string, unknown> }) {
|
|
321
|
-
let didValueChanges = false;
|
|
322
|
-
|
|
323
|
-
for (const [key, field] of Object.entries(this._fields)) {
|
|
324
|
-
const value = options.queryParams[this._transformKeyToQueryParam(key)];
|
|
325
|
-
|
|
326
|
-
const valueDoesNotExist = value === undefined;
|
|
327
|
-
|
|
328
|
-
if (valueDoesNotExist) continue;
|
|
329
|
-
|
|
330
|
-
const valueGotRemoved = value === ET_PROPERTY_REMOVED;
|
|
331
|
-
|
|
332
|
-
if (valueGotRemoved) {
|
|
333
|
-
field.control.setValue(this._getDefaultValue(key), { emitEvent: false });
|
|
334
|
-
didValueChanges = true;
|
|
335
|
-
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
let deserializedValue = value;
|
|
339
|
-
|
|
340
|
-
if (field.data.queryParamToValueTransformFn) {
|
|
341
|
-
deserializedValue = field.data.queryParamToValueTransformFn(value);
|
|
342
|
-
} else if (typeof this._getDefaultValue(key) === 'number' || !isNaN(Number(value))) {
|
|
343
|
-
deserializedValue = transformToNumber(value);
|
|
344
|
-
} else if (typeof this._getDefaultValue(key) === 'boolean' || value === 'true' || value === 'false') {
|
|
345
|
-
deserializedValue = transformToBoolean(value);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const valueIsEqualToCurrent = equal(deserializedValue, field.control.value);
|
|
349
|
-
|
|
350
|
-
if (valueIsEqualToCurrent) continue;
|
|
351
|
-
|
|
352
|
-
field.control.setValue(deserializedValue, { emitEvent: false });
|
|
353
|
-
didValueChanges = true;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return didValueChanges;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
setValue(value: QueryFormValue<T>, options?: QueryFormWriteOptions) {
|
|
360
|
-
this._form._setValue(value, options);
|
|
361
|
-
|
|
362
|
-
if (options?.skipResets) {
|
|
363
|
-
this._skipNextResets = true;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
patchValue(value: Partial<QueryFormValue<T>>, options?: QueryFormWriteOptions) {
|
|
368
|
-
this._form._patchValue(value, options);
|
|
369
|
-
|
|
370
|
-
if (options?.skipResets) {
|
|
371
|
-
this._skipNextResets = true;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
resetFieldToDefault(key: keyof QueryFormValue<T>, options?: QueryFormWriteOptions) {
|
|
376
|
-
const defaultValue = this._getDefaultValue(key as string);
|
|
377
|
-
|
|
378
|
-
this.form.controls[key].setValue(defaultValue);
|
|
379
|
-
|
|
380
|
-
if (options?.skipResets) {
|
|
381
|
-
this._skipNextResets = true;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
resetFieldsToDefault(keys: (keyof QueryFormValue<T>)[], options?: QueryFormWriteOptions) {
|
|
386
|
-
const defaults = keys.reduce(
|
|
387
|
-
(acc, key) => {
|
|
388
|
-
acc[key] = this._getDefaultValue(key as string);
|
|
389
|
-
|
|
390
|
-
return acc;
|
|
391
|
-
},
|
|
392
|
-
{} as Partial<QueryFormValue<T>>,
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
this.patchValue(defaults);
|
|
396
|
-
|
|
397
|
-
if (options?.skipResets) {
|
|
398
|
-
this._skipNextResets = true;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
resetAllFieldsToDefault(options?: QueryFormWriteOptions & { skipFields?: (keyof QueryFormValue<T>)[] }) {
|
|
403
|
-
const keys = Object.keys(this._fields) as (keyof QueryFormValue<T>)[];
|
|
404
|
-
|
|
405
|
-
if (options?.skipFields) {
|
|
406
|
-
for (const key of options.skipFields) {
|
|
407
|
-
const index = keys.indexOf(key);
|
|
408
|
-
|
|
409
|
-
if (index !== -1) {
|
|
410
|
-
keys.splice(index, 1);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
this.resetFieldsToDefault(keys, options);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
private _getDefaultValue(key: string) {
|
|
419
|
-
const val = this._defaultValues[key];
|
|
420
|
-
|
|
421
|
-
if (typeof val === 'string' && val.startsWith(ET_ARR_PREFIX)) {
|
|
422
|
-
return JSON.parse(val.slice(ET_ARR_PREFIX.length));
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return val ?? null;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
private _transformKeyToQueryParam(key: string) {
|
|
429
|
-
if (!this._options?.queryParamPrefix) return key;
|
|
430
|
-
|
|
431
|
-
const prefix =
|
|
432
|
-
typeof this._options?.queryParamPrefix === 'string'
|
|
433
|
-
? this._options.queryParamPrefix
|
|
434
|
-
: this._options.queryParamPrefix();
|
|
435
|
-
|
|
436
|
-
return `${prefix}-${key}`;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private _isDefaultValue(key: string, value: unknown) {
|
|
440
|
-
const normalizedValue = Array.isArray(value) ? `${ET_ARR_PREFIX}${JSON.stringify(value)}` : value;
|
|
441
|
-
|
|
442
|
-
return this._getDefaultValue(key) === normalizedValue;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private _setupFormGroup() {
|
|
446
|
-
const group = new FormGroup({} as QueryFormGroupControls<T>) as unknown as QueryFormGroup;
|
|
447
|
-
|
|
448
|
-
for (const [key, field] of Object.entries(this._fields)) {
|
|
449
|
-
group.addControl(key, field.control);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
group._patchValue = group.patchValue;
|
|
453
|
-
group._setValue = group.setValue;
|
|
454
|
-
|
|
455
|
-
group.patchValue = this.patchValue.bind(this);
|
|
456
|
-
group.setValue = this.setValue.bind(this);
|
|
457
|
-
|
|
458
|
-
return group as unknown as FormGroup<QueryFormGroupControls<T>>;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
private _extractDefaultValues() {
|
|
462
|
-
const defaultValues: Record<string, unknown> = {};
|
|
463
|
-
|
|
464
|
-
for (const [key, field] of Object.entries(this._fields)) {
|
|
465
|
-
const value = field.data.defaultValue !== undefined ? field.data.defaultValue : field.control.value;
|
|
466
|
-
|
|
467
|
-
if (Array.isArray(value)) {
|
|
468
|
-
defaultValues[key] = `${ET_ARR_PREFIX}${JSON.stringify(value)}`;
|
|
469
|
-
} else {
|
|
470
|
-
defaultValues[key] = value;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return defaultValues;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
private _syncViaUrlQueryParams(values: QueryFormValue<T>, replaceUrl?: boolean) {
|
|
478
|
-
const queryParams = { ...clone(this._activatedRoute.snapshot.queryParams) };
|
|
479
|
-
|
|
480
|
-
for (const [key, value] of Object.entries(values)) {
|
|
481
|
-
const queryParamKey = this._transformKeyToQueryParam(key);
|
|
482
|
-
const field = this._fields[key];
|
|
483
|
-
|
|
484
|
-
if (!field) {
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const isDefault = this._isDefaultValue(key, value);
|
|
489
|
-
const writeDefaultToUrl = field.data.appendDefaultValueToUrl === true;
|
|
490
|
-
const writeToUrl = field.data.appendToUrl !== false;
|
|
491
|
-
|
|
492
|
-
if (!writeToUrl || (isDefault && !writeDefaultToUrl)) {
|
|
493
|
-
queryParams[queryParamKey] = undefined;
|
|
494
|
-
} else {
|
|
495
|
-
queryParams[queryParamKey] = field.data.valueToQueryParamTransformFn
|
|
496
|
-
? field.data.valueToQueryParamTransformFn?.(value)
|
|
497
|
-
: value;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
this._zone.run(() => {
|
|
502
|
-
this._router.navigate([], {
|
|
503
|
-
queryParams,
|
|
504
|
-
replaceUrl,
|
|
505
|
-
queryParamsHandling: 'merge',
|
|
506
|
-
});
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
private _handleFormChange(forceOverwrite = false) {
|
|
511
|
-
const currentVal = clone(this._currentFormValue$.value);
|
|
512
|
-
const newVal = clone(this._formValue);
|
|
513
|
-
|
|
514
|
-
if (equal(currentVal, newVal)) {
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (forceOverwrite) {
|
|
519
|
-
this._lastFormValue$.next(newVal);
|
|
520
|
-
} else {
|
|
521
|
-
this._lastFormValue$.next(currentVal);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
this._currentFormValue$.next(newVal);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { FormControl, FormGroup } from '@angular/forms';
|
|
2
|
-
import { QueryField } from './query-form';
|
|
3
|
-
|
|
4
|
-
export interface QueryFieldOptions<T = unknown> {
|
|
5
|
-
/**
|
|
6
|
-
* The default value of the field.
|
|
7
|
-
* Will be set to the initial value of the form control if not specified.
|
|
8
|
-
*/
|
|
9
|
-
defaultValue?: T | null;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Debounce time in milliseconds.
|
|
13
|
-
*/
|
|
14
|
-
debounce?: number;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Whether the debounce should not be applied if the value is falsy.
|
|
18
|
-
* This is useful for fields that can be cleared like a search field.
|
|
19
|
-
*
|
|
20
|
-
* @default false
|
|
21
|
-
*/
|
|
22
|
-
disableDebounceIfFalsy?: boolean;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The form control for the field.
|
|
26
|
-
*/
|
|
27
|
-
control: FormControl<T | null>;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Append the field's value to the url.
|
|
31
|
-
*
|
|
32
|
-
* The field wont be appended to the url if one of the following is true:
|
|
33
|
-
* - It is the default value and `appendDefaultValueToUrl` is `false`
|
|
34
|
-
* - It is an empty string
|
|
35
|
-
* - It is `null`
|
|
36
|
-
* - It is `undefined`
|
|
37
|
-
*
|
|
38
|
-
* @default true
|
|
39
|
-
*/
|
|
40
|
-
appendToUrl?: boolean;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Append the field's value to the url even if it is the default value.
|
|
44
|
-
* Will be ignored if `appendToUrl` is `false`.
|
|
45
|
-
*
|
|
46
|
-
* @default false
|
|
47
|
-
*/
|
|
48
|
-
appendDefaultValueToUrl?: boolean;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Reset the field's value to the default value if one or more of the specified fields are changed.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* // Given this field is a `page` field, the field will be reset if the `limit`, `query` or `search` fields are changed.
|
|
55
|
-
* ['limit', 'query', 'search']
|
|
56
|
-
*/
|
|
57
|
-
isResetBy?: string | string[];
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Whether the field should change the count of current active filters.
|
|
61
|
-
*
|
|
62
|
-
* Will be `true` for these common fields:
|
|
63
|
-
* - `page`
|
|
64
|
-
* - `skip`
|
|
65
|
-
* - `take`
|
|
66
|
-
* - `limit`
|
|
67
|
-
* - `sort`
|
|
68
|
-
* - `sortBy`
|
|
69
|
-
* - `sortOrder`
|
|
70
|
-
* - `query`
|
|
71
|
-
* - `search`
|
|
72
|
-
* @default false
|
|
73
|
-
*/
|
|
74
|
-
skipInFilterCount?: boolean;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* A function that transforms the the value gotten from the url query params to a value required by the field.
|
|
78
|
-
* E.g. for a number field, the value from the url query params is a string, but the field requires a number.
|
|
79
|
-
*
|
|
80
|
-
* The following common transformations are already implemented:
|
|
81
|
-
* - `transformToString`
|
|
82
|
-
* - `transformToStringArray`
|
|
83
|
-
* - `transformToNumber`
|
|
84
|
-
* - `transformToNumberArray`
|
|
85
|
-
* - `transformToBoolean`
|
|
86
|
-
* - `transformToBooleanArray`
|
|
87
|
-
* - `transformToDate` (expects a string in the default js format)
|
|
88
|
-
* - `transformToDateArray` (expects strings in the default js format)
|
|
89
|
-
* - `transformToSort` (expects a string in the format `field:direction`)
|
|
90
|
-
*/
|
|
91
|
-
queryParamToValueTransformFn?: (val: unknown) => T | null;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* A function that transforms the value of the field to a value that can be appended to the url query params.
|
|
95
|
-
*
|
|
96
|
-
* The following common transformations are already implemented:
|
|
97
|
-
* - `transformToSortQueryParam` (returns an object containing `field` and `direction` properties)
|
|
98
|
-
*/
|
|
99
|
-
valueToQueryParamTransformFn?: (val: T | null) => unknown;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
-
export type QueryFormGroupControls<T extends Record<string, QueryField<any>>> = {
|
|
104
|
-
[Property in keyof T]: T[Property]['control'];
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
-
export type QueryFormValue<T extends Record<string, QueryField<any>>> = {
|
|
109
|
-
[Property in keyof T]: T[Property]['control']['value'];
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
export type QueryFormOf<T extends FormGroup> = {
|
|
113
|
-
[K in keyof T['controls']]: QueryField<T['controls'][K]['value']>;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
export interface QueryFormGroup extends FormGroup {
|
|
117
|
-
_setValue: FormGroup['setValue'];
|
|
118
|
-
_patchValue: FormGroup['patchValue'];
|
|
119
|
-
setValue(value: unknown, options?: QueryFormWriteOptions): void;
|
|
120
|
-
patchValue(value: unknown, options?: QueryFormWriteOptions): void;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export interface QueryFormObserveOptions {
|
|
124
|
-
/**
|
|
125
|
-
* Whether the form value should be synced to the url query params.
|
|
126
|
-
* @default true
|
|
127
|
-
*/
|
|
128
|
-
writeToQueryParams?: boolean;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Whether the form value should be updated when the url query params change.
|
|
132
|
-
* @default true
|
|
133
|
-
*/
|
|
134
|
-
syncOnNavigation?: boolean;
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* If true, the navigation will not create a new entry in the browser's history.
|
|
138
|
-
* @default false
|
|
139
|
-
*/
|
|
140
|
-
replaceUrl?: boolean;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
144
|
-
export interface QueryFormValueEvent<T extends Record<string, QueryField<any>>> {
|
|
145
|
-
previousValue: QueryFormValue<T> | null;
|
|
146
|
-
currentValue: QueryFormValue<T>;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface QueryFormWriteOptions {
|
|
150
|
-
/**
|
|
151
|
-
* When true, each change only affects this control, and not its parent.
|
|
152
|
-
* @default false
|
|
153
|
-
*/
|
|
154
|
-
onlySelf?: boolean;
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* When true or not supplied (the default), both the `statusChanges` and `valueChanges`
|
|
158
|
-
* observables emit events with the latest status and value when the control value is updated.
|
|
159
|
-
* When false, no events are emitted.
|
|
160
|
-
* @default true
|
|
161
|
-
*/
|
|
162
|
-
emitEvent?: boolean;
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* When true, the `resetBy` conditions will be ignored for this write.
|
|
166
|
-
* @default false
|
|
167
|
-
*/
|
|
168
|
-
skipResets?: boolean;
|
|
169
|
-
}
|