0 Tage Serie
javascript / expert
Snippet

Komponierbare Validator-Factories mit Funktionen höherer Ordnung für wiederverwendbare Constraints

Statt für jedes Formular einen monolithischen Validator von Hand zu schreiben, behandelt dieses Muster Validatoren als erstklassige Werte, die von parametrisierten Factory-Funktionen erzeugt werden, und kombiniert sie mit einer Funktion höherer Ordnung composeAnd, die jedes nicht-null ValidationErrors-Objekt zu einem zusammenführt. Jede Factory schließt über ihre Konfiguration (eine Anzahl, einen Pfad zu einem Geschwister-Control) und liefert eine reine ValidatorFn zurück, sodass dasselbe minDistinctChars(4) über unabhängige Formulare mit unterschiedlichen Schwellenwerten wiederverwendet werden kann — und composeAnd funktioniert mit beliebig vielen Validatoren, ohne dass der Aufrufer wissen muss, wie viele gleichzeitig fehlgeschlagen sind.

snippet.js
javascript
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
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
function minDistinctChars(count: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value: string = control.value ?? '';
const distinct = new Set(value.split('')).size;
return distinct >= count ? null : { minDistinctChars: { required: count, actual: distinct } };
};
}
function composeAnd(...validators: ValidatorFn[]): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
return validators.reduce<ValidationErrors | null>((acc, validate) => {
const result = validate(control);
return result ? { ...acc, ...result } : acc;
}, null);
};
}
function notEqualTo(otherControlPath: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const sibling = control.parent?.get(otherControlPath);
if (!sibling) return null;
return control.value === sibling.value ? { notEqualTo: { path: otherControlPath } } : null;
};
}
const passwordValidator = composeAnd(minDistinctChars(4), notEqualTo('username'));
angular
Erklärung
1
function minDistinctChars(count: number): ValidatorFn {
Die Factory liefert eine Funktion statt selbst ein Validator zu sein und fängt count in einem Closure ein, das bei jedem künftigen Aufruf verfügbar ist.
2
const distinct = new Set(value.split('')).size;
Ein Set dedupliziert Zeichen in einem Durchgang und liefert eine O(n)-Distinct-Zählung ohne manuelle Buchführung.
3
function composeAnd(...validators: ValidatorFn[]): ValidatorFn {
Rest-Parameter erlauben composeAnd eine beliebige Anzahl von Validatoren, was es zu einem generischen Kombinator statt fest-arity Klebecode macht.
4
return result ? { ...acc, ...result } : acc;
Das Spreading führt Fehlerschlüssel mehrerer fehlschlagender Validatoren zusammen, statt nur den letzten anzuzeigen, sodass die UI jede verletzte Regel zeigen kann.
5
const sibling = control.parent?.get(otherControlPath);
Optional Chaining schützt vor einem Aufruf dieses Validators, bevor das Control an seine übergeordnete FormGroup angehängt ist, wenn parent noch null ist.