Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 1x 805x 805x 2415x 805x 1x 267x 267x 267x 538x 57x 481x 43x 438x 267x 171x 538x 267x | import type { Signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { PristineChangeEvent, StatusChangeEvent, TouchedChangeEvent } from '@angular/forms';
import type { AbstractControl, ControlEvent } from '@angular/forms';
import { distinctUntilChanged, map, scan } from 'rxjs';
import type { Observable } from 'rxjs';
/**
* Angular AbstractControl properties that are used to determine if the control should signal an
* invalid state.
*/
interface ControlProperties {
/** Control value has been modified. */
readonly dirty: boolean;
/** Control value fails validation. */
readonly invalid: boolean;
/** Control has been focused in the view. */
readonly touched: boolean;
}
/**
* When all the ControlProperties are true then the Control is invalid.
*/
const isInvalid = (properties: ControlProperties): boolean => {
let invalid = true;
for (const val of Object.values(properties)) {
invalid &&= Boolean(val);
}
return invalid;
};
/**
* Create an Angular Signal that flags as modified and invalid based on the Control properties.
*
* 1. Invalid - the value fails validation checks.
* 2. Dirty - the value is different from the initial value.
* 3. Touched - the Control has been focused during the current view.
*
* This ensures that the aria-invalid attribute is only set on Controls that the user has interacted
* with.
*/
export const controlInvalidSignal = (control: AbstractControl): Signal<boolean> => {
const defaultProperties: ControlProperties = {
dirty: control.dirty,
invalid: control.invalid,
touched: control.touched,
};
const initialValue = isInvalid(defaultProperties);
const controlEvents$: Observable<boolean> = control.events.pipe(
scan(
(current: ControlProperties, event: ControlEvent<unknown>): ControlProperties => {
if (event instanceof PristineChangeEvent) {
return { ...current, dirty: !event.pristine };
}
if (event instanceof TouchedChangeEvent) {
return { ...current, touched: event.touched };
}
if (event instanceof StatusChangeEvent) {
return { ...current, invalid: event.status === 'INVALID' };
}
return current;
},
defaultProperties,
),
map((properties: ControlProperties): boolean => isInvalid(properties)),
distinctUntilChanged(),
);
return toSignal(controlEvents$, { initialValue });
};
|