ngx-form-signal 1.18.0
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/README.md +24 -0
- package/ng-package.json +7 -0
- package/package.json +13 -0
- package/src/lib/form-signal.ts +75 -0
- package/src/lib/helpers/form-dirty-signal.ts +46 -0
- package/src/lib/helpers/form-error-signal.ts +28 -0
- package/src/lib/helpers/form-invalid-signal.ts +23 -0
- package/src/lib/helpers/form-snapshot-signal.ts +37 -0
- package/src/lib/helpers/form-status-signal.ts +57 -0
- package/src/lib/helpers/form-touched-signal.ts +48 -0
- package/src/lib/helpers/form-value-signal.ts +48 -0
- package/src/lib/types/form-signal-options.ts +20 -0
- package/src/lib/types/form-signal-type.ts +68 -0
- package/src/lib/types/form-type.ts +18 -0
- package/src/public-api.ts +8 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# NgxFormSignal
|
|
2
|
+
|
|
3
|
+
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.2.0.
|
|
4
|
+
|
|
5
|
+
## Code scaffolding
|
|
6
|
+
|
|
7
|
+
Run `ng generate component component-name --project ngx-form-signal` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ngx-form-signal`.
|
|
8
|
+
> Note: Don't forget to add `--project ngx-form-signal` or else it will be added to the default project in your `angular.json` file.
|
|
9
|
+
|
|
10
|
+
## Build
|
|
11
|
+
|
|
12
|
+
Run `ng build ngx-form-signal` to build the project. The build artifacts will be stored in the `dist/` directory.
|
|
13
|
+
|
|
14
|
+
## Publishing
|
|
15
|
+
|
|
16
|
+
After building your library with `ng build ngx-form-signal`, go to the dist folder `cd dist/ngx-form-signal` and run `npm publish`.
|
|
17
|
+
|
|
18
|
+
## Running unit tests
|
|
19
|
+
|
|
20
|
+
Run `ng test ngx-form-signal` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
|
21
|
+
|
|
22
|
+
## Further help
|
|
23
|
+
|
|
24
|
+
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
package/ng-package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertInInjectionContext,
|
|
3
|
+
isSignal,
|
|
4
|
+
Signal,
|
|
5
|
+
signal,
|
|
6
|
+
} from '@angular/core';
|
|
7
|
+
import { buildFormDirtySignal } from './helpers/form-dirty-signal';
|
|
8
|
+
import { buildFormErrorSignal } from './helpers/form-error-signal';
|
|
9
|
+
import { buildFormSnapshotSignal } from './helpers/form-snapshot-signal';
|
|
10
|
+
import { buildFormStatusSignal } from './helpers/form-status-signal';
|
|
11
|
+
import { buildFormTouchedSignal } from './helpers/form-touched-signal';
|
|
12
|
+
import { buildFormValueSignal } from './helpers/form-value-signal';
|
|
13
|
+
import {
|
|
14
|
+
buildDefaultFormSignalOptions,
|
|
15
|
+
FormSignalOptions,
|
|
16
|
+
} from './types/form-signal-options';
|
|
17
|
+
import { FormSignal, FormSignalState } from './types/form-signal-type';
|
|
18
|
+
import { OptionalFormFromType } from './types/form-type';
|
|
19
|
+
|
|
20
|
+
export function formSignal<T = any>(
|
|
21
|
+
form: Signal<OptionalFormFromType<T>> | OptionalFormFromType<T>,
|
|
22
|
+
options: FormSignalOptions = buildDefaultFormSignalOptions<T>()
|
|
23
|
+
): FormSignal<T> {
|
|
24
|
+
const formAsSignal = isSignal(form) ? form : signal(form);
|
|
25
|
+
if (!options.injector) {
|
|
26
|
+
assertInInjectionContext(() => {});
|
|
27
|
+
}
|
|
28
|
+
const { value$, rawValue$, valueChangeSubscription$ } =
|
|
29
|
+
buildFormValueSignal<T>(formAsSignal, options);
|
|
30
|
+
const {
|
|
31
|
+
status$,
|
|
32
|
+
valid$,
|
|
33
|
+
invalid$,
|
|
34
|
+
pending$,
|
|
35
|
+
disabled$,
|
|
36
|
+
enabled$,
|
|
37
|
+
statusChangeSubscription$,
|
|
38
|
+
} = buildFormStatusSignal<T>(formAsSignal, options);
|
|
39
|
+
const { touched$, untouched$, touchedChangeSubscription$ } =
|
|
40
|
+
buildFormTouchedSignal(formAsSignal, options);
|
|
41
|
+
const { dirty$, pristine$, dirtyChangeSubscription$ } = buildFormDirtySignal(
|
|
42
|
+
formAsSignal,
|
|
43
|
+
options
|
|
44
|
+
);
|
|
45
|
+
const errors$ = buildFormErrorSignal(formAsSignal, value$, status$, options);
|
|
46
|
+
|
|
47
|
+
const formSignals: FormSignalState<T> = {
|
|
48
|
+
status: status$.asReadonly(),
|
|
49
|
+
value: value$.asReadonly(),
|
|
50
|
+
rawValue: rawValue$.asReadonly(),
|
|
51
|
+
touched: touched$.asReadonly(),
|
|
52
|
+
untouched: untouched$,
|
|
53
|
+
dirty: dirty$.asReadonly(),
|
|
54
|
+
pristine: pristine$,
|
|
55
|
+
valid: valid$,
|
|
56
|
+
invalid: invalid$,
|
|
57
|
+
pending: pending$,
|
|
58
|
+
disabled: disabled$,
|
|
59
|
+
enabled: enabled$,
|
|
60
|
+
errors: errors$,
|
|
61
|
+
subscriptions: {
|
|
62
|
+
valueChangeSubscription: valueChangeSubscription$.asReadonly(),
|
|
63
|
+
statusChangeSubscription: statusChangeSubscription$.asReadonly(),
|
|
64
|
+
touchedChangeSubscription: touchedChangeSubscription$.asReadonly(),
|
|
65
|
+
dirtyChangeSubscription: dirtyChangeSubscription$.asReadonly(),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const snapshot$ = buildFormSnapshotSignal(formSignals);
|
|
70
|
+
const formSignalObj = (() => snapshot$()) as FormSignal<T>;
|
|
71
|
+
|
|
72
|
+
Object.setPrototypeOf(formSignalObj, formSignals);
|
|
73
|
+
|
|
74
|
+
return formSignalObj;
|
|
75
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { computed, effect, Signal, signal, untracked } from '@angular/core';
|
|
2
|
+
import { PristineChangeEvent } from '@angular/forms';
|
|
3
|
+
import { filter, Subscription } from 'rxjs';
|
|
4
|
+
import { FormSignalOptions } from '../types/form-signal-options';
|
|
5
|
+
import { OptionalFormFromType } from '../types/form-type';
|
|
6
|
+
|
|
7
|
+
export function buildFormDirtySignal(
|
|
8
|
+
formAsSignal: Signal<OptionalFormFromType<any>>,
|
|
9
|
+
options: FormSignalOptions
|
|
10
|
+
) {
|
|
11
|
+
const dirty$ = signal<boolean>(!!formAsSignal()?.dirty, {
|
|
12
|
+
equal: options.equalityFns?.dirtyEquality,
|
|
13
|
+
});
|
|
14
|
+
const dirtyChangeSubscription$ = signal<Subscription | null>(null);
|
|
15
|
+
|
|
16
|
+
const formDirtyChangeEffect = effect(
|
|
17
|
+
(onCleanup: Function) => {
|
|
18
|
+
const form = formAsSignal();
|
|
19
|
+
untracked(() => {
|
|
20
|
+
const setDirty = () => {
|
|
21
|
+
untracked(() => {
|
|
22
|
+
dirty$.set(!!form?.dirty);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
dirtyChangeSubscription$()?.unsubscribe();
|
|
27
|
+
dirtyChangeSubscription$.set(
|
|
28
|
+
form?.events
|
|
29
|
+
.pipe(filter((e) => e instanceof PristineChangeEvent))
|
|
30
|
+
.subscribe((e) => {
|
|
31
|
+
setDirty();
|
|
32
|
+
}) ?? null
|
|
33
|
+
);
|
|
34
|
+
setDirty();
|
|
35
|
+
});
|
|
36
|
+
onCleanup(
|
|
37
|
+
() => untracked(() => dirtyChangeSubscription$())?.unsubscribe()
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
{ injector: options.injector }
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const pristine$ = computed(() => !dirty$());
|
|
44
|
+
|
|
45
|
+
return { dirty$, pristine$, dirtyChangeSubscription$ };
|
|
46
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Signal,
|
|
3
|
+
WritableSignal,
|
|
4
|
+
computed,
|
|
5
|
+
isSignal,
|
|
6
|
+
signal,
|
|
7
|
+
} from '@angular/core';
|
|
8
|
+
import { FormControlStatus } from '@angular/forms';
|
|
9
|
+
import { FormSignalOptions } from '../types/form-signal-options';
|
|
10
|
+
import { FormFromType, OptionalFormFromType } from '../types/form-type';
|
|
11
|
+
|
|
12
|
+
export function buildFormErrorSignal<T = any>(
|
|
13
|
+
form: Signal<OptionalFormFromType<T>> | OptionalFormFromType<T>,
|
|
14
|
+
valueSignal: WritableSignal<FormFromType<T>['value'] | null>,
|
|
15
|
+
statusSignal: WritableSignal<FormControlStatus | null>,
|
|
16
|
+
options: FormSignalOptions<T>
|
|
17
|
+
) {
|
|
18
|
+
const formAsSignal = isSignal(form) ? form : signal(form);
|
|
19
|
+
|
|
20
|
+
return computed(
|
|
21
|
+
() => {
|
|
22
|
+
const v = valueSignal();
|
|
23
|
+
const s = statusSignal();
|
|
24
|
+
return formAsSignal()?.errors ?? null;
|
|
25
|
+
},
|
|
26
|
+
{ equal: options.equalityFns?.errorsEquality }
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Signal,
|
|
3
|
+
WritableSignal,
|
|
4
|
+
computed,
|
|
5
|
+
isSignal,
|
|
6
|
+
signal,
|
|
7
|
+
} from '@angular/core';
|
|
8
|
+
import { FormControlStatus } from '@angular/forms';
|
|
9
|
+
import { FormFromType, OptionalFormFromType } from '../types/form-type';
|
|
10
|
+
|
|
11
|
+
export function buildFormInvalidSignal<T = any>(
|
|
12
|
+
form: Signal<OptionalFormFromType<T>> | OptionalFormFromType<T>,
|
|
13
|
+
valueSignal: WritableSignal<FormFromType<T>['value'] | null>,
|
|
14
|
+
statusSignal: WritableSignal<FormControlStatus | null>
|
|
15
|
+
) {
|
|
16
|
+
const formAsSignal = isSignal(form) ? form : signal(form);
|
|
17
|
+
|
|
18
|
+
return computed(() => {
|
|
19
|
+
const v = valueSignal();
|
|
20
|
+
const s = statusSignal();
|
|
21
|
+
return !!formAsSignal()?.invalid;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { computed } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
FormSignalState,
|
|
4
|
+
FormSnapshotSignalState,
|
|
5
|
+
} from '../types/form-signal-type';
|
|
6
|
+
|
|
7
|
+
export function buildFormSnapshotSignal<T = any>(
|
|
8
|
+
formSignalState: FormSignalState<T>
|
|
9
|
+
) {
|
|
10
|
+
return computed<FormSnapshotSignalState<T>>(() => {
|
|
11
|
+
return {
|
|
12
|
+
status: formSignalState.status(),
|
|
13
|
+
value: formSignalState.value(),
|
|
14
|
+
rawValue: formSignalState.rawValue(),
|
|
15
|
+
touched: formSignalState.touched(),
|
|
16
|
+
untouched: formSignalState.untouched(),
|
|
17
|
+
dirty: formSignalState.dirty(),
|
|
18
|
+
pristine: formSignalState.pristine(),
|
|
19
|
+
valid: formSignalState.valid(),
|
|
20
|
+
invalid: formSignalState.invalid(),
|
|
21
|
+
pending: formSignalState.pending(),
|
|
22
|
+
disabled: formSignalState.disabled(),
|
|
23
|
+
enabled: formSignalState.enabled(),
|
|
24
|
+
errors: formSignalState.errors(),
|
|
25
|
+
subscriptions: {
|
|
26
|
+
valueChangeSubscription:
|
|
27
|
+
formSignalState.subscriptions.valueChangeSubscription(),
|
|
28
|
+
statusChangeSubscription:
|
|
29
|
+
formSignalState.subscriptions.statusChangeSubscription(),
|
|
30
|
+
touchedChangeSubscription:
|
|
31
|
+
formSignalState.subscriptions.touchedChangeSubscription(),
|
|
32
|
+
dirtyChangeSubscription:
|
|
33
|
+
formSignalState.subscriptions.dirtyChangeSubscription(),
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { computed, effect, Signal, signal, untracked } from '@angular/core';
|
|
2
|
+
import { FormControlStatus } from '@angular/forms';
|
|
3
|
+
import { Subscription } from 'rxjs';
|
|
4
|
+
import { FormSignalOptions } from '../types/form-signal-options';
|
|
5
|
+
import { OptionalFormFromType } from '../types/form-type';
|
|
6
|
+
|
|
7
|
+
export function buildFormStatusSignal<T = any>(
|
|
8
|
+
formAsSignal: Signal<OptionalFormFromType<T>>,
|
|
9
|
+
options: FormSignalOptions<T>
|
|
10
|
+
) {
|
|
11
|
+
const equal = options.equalityFns?.statusEquality ?? (() => false);
|
|
12
|
+
const status$ = signal<FormControlStatus | null>(
|
|
13
|
+
formAsSignal()?.status ?? null,
|
|
14
|
+
{ equal }
|
|
15
|
+
);
|
|
16
|
+
const statusChangeSubscription$ = signal<Subscription | null>(null);
|
|
17
|
+
const formStatusChangeEffect = effect(
|
|
18
|
+
(onCleanup: Function) => {
|
|
19
|
+
const form = formAsSignal();
|
|
20
|
+
untracked(() => {
|
|
21
|
+
const setStatus = () => {
|
|
22
|
+
untracked(() => {
|
|
23
|
+
status$.set(form?.status ?? null);
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
statusChangeSubscription$()?.unsubscribe();
|
|
28
|
+
statusChangeSubscription$.set(
|
|
29
|
+
form?.statusChanges.subscribe(() => {
|
|
30
|
+
setStatus();
|
|
31
|
+
}) ?? null
|
|
32
|
+
);
|
|
33
|
+
setStatus();
|
|
34
|
+
});
|
|
35
|
+
onCleanup(
|
|
36
|
+
() => untracked(() => statusChangeSubscription$())?.unsubscribe()
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
{ injector: options.injector }
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const valid$ = computed(() => status$() === 'VALID');
|
|
43
|
+
const invalid$ = computed(() => status$() === 'INVALID');
|
|
44
|
+
const pending$ = computed(() => status$() == 'PENDING');
|
|
45
|
+
const disabled$ = computed(() => status$() === 'DISABLED');
|
|
46
|
+
const enabled$ = computed(() => status$() !== 'DISABLED');
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
status$,
|
|
50
|
+
valid$,
|
|
51
|
+
invalid$,
|
|
52
|
+
pending$,
|
|
53
|
+
disabled$,
|
|
54
|
+
enabled$,
|
|
55
|
+
statusChangeSubscription$,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { computed, effect, Signal, signal, untracked } from '@angular/core';
|
|
2
|
+
import { TouchedChangeEvent } from '@angular/forms';
|
|
3
|
+
import { filter, Subscription } from 'rxjs';
|
|
4
|
+
import { FormSignalOptions } from '../types/form-signal-options';
|
|
5
|
+
import { OptionalFormFromType } from '../types/form-type';
|
|
6
|
+
|
|
7
|
+
export function buildFormTouchedSignal(
|
|
8
|
+
formAsSignal: Signal<OptionalFormFromType<any>>,
|
|
9
|
+
options: FormSignalOptions
|
|
10
|
+
) {
|
|
11
|
+
const touched$ = signal<boolean>(!!formAsSignal()?.touched, {
|
|
12
|
+
equal: options.equalityFns?.touchedEquality,
|
|
13
|
+
});
|
|
14
|
+
const touchedChangeSubscription$ = signal<Subscription | null>(null);
|
|
15
|
+
|
|
16
|
+
const formTouchedChangeEffect = effect(
|
|
17
|
+
(onCleanup: Function) => {
|
|
18
|
+
const form = formAsSignal();
|
|
19
|
+
untracked(() => {
|
|
20
|
+
const setTouched = () => {
|
|
21
|
+
untracked(() => {
|
|
22
|
+
touched$.set(!!form?.touched);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
touchedChangeSubscription$()?.unsubscribe();
|
|
27
|
+
touchedChangeSubscription$.set(
|
|
28
|
+
form?.events
|
|
29
|
+
.pipe(filter((e) => e instanceof TouchedChangeEvent))
|
|
30
|
+
.subscribe((e) => {
|
|
31
|
+
setTouched();
|
|
32
|
+
}) ?? null
|
|
33
|
+
);
|
|
34
|
+
setTouched();
|
|
35
|
+
});
|
|
36
|
+
onCleanup(
|
|
37
|
+
() => untracked(() => touchedChangeSubscription$())?.unsubscribe()
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
{ injector: options.injector }
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const untouched$ = computed(() => !touched$(), {
|
|
44
|
+
equal: options.equalityFns?.touchedEquality,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return { touched$, untouched$, touchedChangeSubscription$ };
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { effect, Signal, signal, untracked } from '@angular/core';
|
|
2
|
+
import { AbstractControl } from '@angular/forms';
|
|
3
|
+
import { Subscription } from 'rxjs';
|
|
4
|
+
import { FormSignalOptions } from '../types/form-signal-options';
|
|
5
|
+
import { FormFromType, OptionalFormFromType } from '../types/form-type';
|
|
6
|
+
|
|
7
|
+
export function buildFormValueSignal<T = any>(
|
|
8
|
+
formAsSignal: Signal<OptionalFormFromType<T>>,
|
|
9
|
+
options: FormSignalOptions<T>
|
|
10
|
+
) {
|
|
11
|
+
const value$ = signal<FormFromType<T>['value'] | null>(
|
|
12
|
+
formAsSignal()?.value ?? null,
|
|
13
|
+
{ equal: options.equalityFns?.valueEquality ?? (() => false) }
|
|
14
|
+
);
|
|
15
|
+
const rawValue$ = signal<FormFromType<T>['value'] | null>(
|
|
16
|
+
formAsSignal()?.getRawValue() ?? null,
|
|
17
|
+
{
|
|
18
|
+
equal: options.equalityFns?.valueEquality ?? (() => false),
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
const valueChangeSubscription$ = signal<Subscription | null>(null);
|
|
22
|
+
const formValueChangeEffect = effect(
|
|
23
|
+
(onCleanup: Function) => {
|
|
24
|
+
const form = formAsSignal() as AbstractControl<T> | null;
|
|
25
|
+
untracked(() => {
|
|
26
|
+
const setValue = () => {
|
|
27
|
+
untracked(() => {
|
|
28
|
+
value$.set(form?.value ?? null);
|
|
29
|
+
rawValue$.set(form?.getRawValue() ?? null);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
valueChangeSubscription$()?.unsubscribe();
|
|
34
|
+
valueChangeSubscription$.set(
|
|
35
|
+
form?.valueChanges.subscribe(() => {
|
|
36
|
+
setValue();
|
|
37
|
+
}) ?? null
|
|
38
|
+
);
|
|
39
|
+
setValue();
|
|
40
|
+
});
|
|
41
|
+
onCleanup(
|
|
42
|
+
() => untracked(() => valueChangeSubscription$())?.unsubscribe()
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
{ injector: options.injector }
|
|
46
|
+
);
|
|
47
|
+
return { value$, rawValue$, valueChangeSubscription$ };
|
|
48
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Injector, ValueEqualityFn } from '@angular/core';
|
|
2
|
+
import { FormControlStatus, ValidationErrors } from '@angular/forms';
|
|
3
|
+
import { FormFromType } from './form-type';
|
|
4
|
+
|
|
5
|
+
export type FormSignalEqualityOptions<T = any> = {
|
|
6
|
+
dirtyEquality: ValueEqualityFn<boolean>;
|
|
7
|
+
touchedEquality: ValueEqualityFn<boolean>;
|
|
8
|
+
valueEquality: ValueEqualityFn<FormFromType<T>['value'] | null>;
|
|
9
|
+
statusEquality: ValueEqualityFn<FormControlStatus | null>;
|
|
10
|
+
errorsEquality: ValueEqualityFn<ValidationErrors | null>;
|
|
11
|
+
};
|
|
12
|
+
export type FormSignalOptions<T = any> = {
|
|
13
|
+
injector?: Injector;
|
|
14
|
+
equalityFns?: Partial<FormSignalEqualityOptions<T>>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const defaultFormSignalOptions: FormSignalOptions = {};
|
|
18
|
+
export function buildDefaultFormSignalOptions<T = any>(): FormSignalOptions<T> {
|
|
19
|
+
return structuredClone(defaultFormSignalOptions);
|
|
20
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { buildFormDirtySignal } from '../helpers/form-dirty-signal';
|
|
2
|
+
import { buildFormErrorSignal } from '../helpers/form-error-signal';
|
|
3
|
+
import { buildFormStatusSignal } from '../helpers/form-status-signal';
|
|
4
|
+
import { buildFormTouchedSignal } from '../helpers/form-touched-signal';
|
|
5
|
+
import { buildFormValueSignal } from '../helpers/form-value-signal';
|
|
6
|
+
|
|
7
|
+
export type FormSignalSubscriptionsState<T = any> = {
|
|
8
|
+
valueChangeSubscription: ReturnType<
|
|
9
|
+
ReturnType<
|
|
10
|
+
typeof buildFormValueSignal<T>
|
|
11
|
+
>['valueChangeSubscription$']['asReadonly']
|
|
12
|
+
>;
|
|
13
|
+
statusChangeSubscription: ReturnType<
|
|
14
|
+
ReturnType<
|
|
15
|
+
typeof buildFormStatusSignal
|
|
16
|
+
>['statusChangeSubscription$']['asReadonly']
|
|
17
|
+
>;
|
|
18
|
+
touchedChangeSubscription: ReturnType<
|
|
19
|
+
ReturnType<
|
|
20
|
+
typeof buildFormTouchedSignal
|
|
21
|
+
>['touchedChangeSubscription$']['asReadonly']
|
|
22
|
+
>;
|
|
23
|
+
dirtyChangeSubscription: ReturnType<
|
|
24
|
+
ReturnType<
|
|
25
|
+
typeof buildFormDirtySignal
|
|
26
|
+
>['dirtyChangeSubscription$']['asReadonly']
|
|
27
|
+
>;
|
|
28
|
+
};
|
|
29
|
+
export type FormSignalState<T = any> = {
|
|
30
|
+
status: ReturnType<
|
|
31
|
+
ReturnType<typeof buildFormStatusSignal>['status$']['asReadonly']
|
|
32
|
+
>;
|
|
33
|
+
value: ReturnType<
|
|
34
|
+
ReturnType<typeof buildFormValueSignal<T>>['value$']['asReadonly']
|
|
35
|
+
>;
|
|
36
|
+
rawValue: ReturnType<
|
|
37
|
+
ReturnType<typeof buildFormValueSignal<T>>['rawValue$']['asReadonly']
|
|
38
|
+
>;
|
|
39
|
+
touched: ReturnType<
|
|
40
|
+
ReturnType<typeof buildFormTouchedSignal>['touched$']['asReadonly']
|
|
41
|
+
>;
|
|
42
|
+
untouched: ReturnType<typeof buildFormTouchedSignal>['untouched$'];
|
|
43
|
+
dirty: ReturnType<
|
|
44
|
+
ReturnType<typeof buildFormDirtySignal>['dirty$']['asReadonly']
|
|
45
|
+
>;
|
|
46
|
+
pristine: ReturnType<typeof buildFormDirtySignal>['pristine$'];
|
|
47
|
+
valid: ReturnType<typeof buildFormStatusSignal>['valid$'];
|
|
48
|
+
invalid: ReturnType<typeof buildFormStatusSignal>['invalid$'];
|
|
49
|
+
pending: ReturnType<typeof buildFormStatusSignal>['pending$'];
|
|
50
|
+
disabled: ReturnType<typeof buildFormStatusSignal>['disabled$'];
|
|
51
|
+
enabled: ReturnType<typeof buildFormStatusSignal>['enabled$'];
|
|
52
|
+
errors: ReturnType<typeof buildFormErrorSignal>;
|
|
53
|
+
subscriptions: FormSignalSubscriptionsState<T>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type FormSnapshotSignalSubscriptionsState<T = any> = {
|
|
57
|
+
[x in keyof FormSignalSubscriptionsState<T>]: ReturnType<
|
|
58
|
+
FormSignalSubscriptionsState<T>[x]
|
|
59
|
+
>;
|
|
60
|
+
};
|
|
61
|
+
export type FormSnapshotSignalState<T = any> = {
|
|
62
|
+
[x in keyof Omit<FormSignalState<T>, 'subscriptions'>]: ReturnType<
|
|
63
|
+
FormSignalState<T>[x]
|
|
64
|
+
>;
|
|
65
|
+
} & { subscriptions: FormSnapshotSignalSubscriptionsState<T> };
|
|
66
|
+
|
|
67
|
+
export type FormSignal<T = any> = FormSignalState<T> &
|
|
68
|
+
(() => FormSnapshotSignalState<T>);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbstractControl,
|
|
3
|
+
FormArray,
|
|
4
|
+
FormControl,
|
|
5
|
+
FormGroup,
|
|
6
|
+
} from '@angular/forms';
|
|
7
|
+
|
|
8
|
+
export type FormFromType<T> = T extends { [K in keyof T]: AbstractControl<any> }
|
|
9
|
+
? FormGroup<T>
|
|
10
|
+
: T extends AbstractControl<any>
|
|
11
|
+
? FormArray<T>
|
|
12
|
+
: FormControl<T>;
|
|
13
|
+
|
|
14
|
+
export type OptionalFormFromType<T = any> =
|
|
15
|
+
| (T extends { [K in keyof T]: AbstractControl<any> } ? FormGroup<T> : never)
|
|
16
|
+
| FormControl<T>
|
|
17
|
+
| (T extends AbstractControl<any> ? FormArray<T> : never)
|
|
18
|
+
| null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/lib",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"inlineSources": true,
|
|
10
|
+
"types": []
|
|
11
|
+
},
|
|
12
|
+
"exclude": [
|
|
13
|
+
"**/*.spec.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "./tsconfig.lib.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"declarationMap": false
|
|
7
|
+
},
|
|
8
|
+
"angularCompilerOptions": {
|
|
9
|
+
"compilationMode": "partial"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/spec",
|
|
7
|
+
"types": [
|
|
8
|
+
"jasmine"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"include": [
|
|
12
|
+
"**/*.spec.ts",
|
|
13
|
+
"**/*.d.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|