1
- import { AbstractControl , AbstractControlDirective , FormControlStatus } from "@angular/forms" ;
2
- import { Observable , of , map , distinctUntilChanged , startWith , delay } from "rxjs" ;
1
+ import { AbstractControl , PristineChangeEvent , StatusChangeEvent } from "@angular/forms" ;
2
+ import { Observable , map , distinctUntilChanged , startWith , filter , combineLatest , of } from "rxjs" ;
3
3
4
4
import { CommandCreator , ICommand } from "./command.model" ;
5
5
import { Command } from "./command" ;
6
+ import { type Signal , computed } from "@angular/core" ;
6
7
7
8
/** Determines whether the arg object is of type `Command`. */
8
9
export function isCommand ( arg : unknown ) : arg is ICommand {
@@ -27,23 +28,54 @@ export interface CanExecuteFormOptions {
27
28
dirty ?: boolean ;
28
29
}
29
30
30
- /** Get form is valid as an observable. */
31
+ const CAN_EXECUTE_FORM_OPTIONS_DEFAULTS = Object . freeze < CanExecuteFormOptions > ( {
32
+ validity : true ,
33
+ dirty : true ,
34
+ } )
35
+
36
+ /** Get can execute from form validity/pristine as an observable. */
31
37
export function canExecuteFromNgForm (
32
- form : AbstractControl | AbstractControlDirective ,
38
+ form : AbstractControl ,
33
39
options ?: CanExecuteFormOptions
34
40
) : Observable < boolean > {
35
- const opts : CanExecuteFormOptions = { validity : true , dirty : true , ...options } ;
41
+ const opts : CanExecuteFormOptions = options ?
42
+ { ...CAN_EXECUTE_FORM_OPTIONS_DEFAULTS , ...options }
43
+ : CAN_EXECUTE_FORM_OPTIONS_DEFAULTS ;
44
+
45
+ const pristine$ = opts . dirty
46
+ ? form . events . pipe (
47
+ filter ( x => x instanceof PristineChangeEvent ) ,
48
+ map ( x => x . pristine ) ,
49
+ distinctUntilChanged ( ) ,
50
+ startWith ( form . pristine ) ,
51
+ ) : of ( true ) ;
36
52
37
- return form . statusChanges
38
- ? ( form . statusChanges as Observable < FormControlStatus > ) . pipe ( // todo: remove cast when working correctly
39
- delay ( 0 ) ,
40
- startWith ( form . valid ) ,
41
- map ( ( ) => ! ! ( ! opts . validity || form . valid ) && ! ! ( ! opts . dirty || form . dirty ) ) ,
53
+ const valid$ = opts . validity
54
+ ? form . events . pipe (
55
+ filter ( x => x instanceof StatusChangeEvent ) ,
56
+ map ( x => x . status === "VALID" ) ,
42
57
distinctUntilChanged ( ) ,
43
- )
44
- : of ( true ) ;
58
+ startWith ( form . pristine ) ,
59
+ ) : of ( true ) ;
60
+
61
+ return combineLatest ( [ pristine$ , valid$ ] ) . pipe (
62
+ map ( ( [ pristine , valid ] ) => ! ! ( ! opts . validity || valid ) && ! ! ( ! opts . dirty || ! pristine ) ) ,
63
+ distinctUntilChanged ( ) ,
64
+ ) ;
45
65
}
46
66
67
+ /** Can executed based on valid/dirty signal inputs. */
68
+ export function canExecuteFromSignals (
69
+ signals : { valid : Signal < boolean > , dirty : Signal < boolean > } ,
70
+ options ?: CanExecuteFormOptions
71
+ ) : Signal < boolean > {
72
+ const opts : CanExecuteFormOptions = options ?
73
+ { ...CAN_EXECUTE_FORM_OPTIONS_DEFAULTS , ...options }
74
+ : CAN_EXECUTE_FORM_OPTIONS_DEFAULTS ;
75
+ return computed ( ( ) => ! ! ( ! opts . validity || signals . valid ( ) ) && ! ! ( ! opts . dirty || signals . dirty ( ) ) ) ;
76
+ }
77
+
78
+
47
79
function isAssumedType < T = Record < string , unknown > > ( x : unknown ) : x is Partial < T > {
48
80
return x !== null && typeof x === "object" ;
49
81
}
0 commit comments