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 }); }; |