Formulare dürften in den meisten Business-Angular-Anwendungen zum täglich Brot gehören. Eigene Komponenten in ein Formular einzubetten ist dabei gar nicht schwer und erlaubt ein nahtloses Zusammenspiel mit FormGroup, Validator & Co.
Ziel ist es, die eigenen Komponenten nahtlos in ein Formular einzubinden. Es sollte keine Rollen spielen, ob wir zur Erfassung unserer Werte ein einfaches Input-Field ein mat-slider aus der Angular Material Bibliothek oder eine selbst geschriebene Komponente verwenden:
this.form = fb.group({
number: fb.control<CustomerType | null>(null, [Validators.required, Validators.minLength(5)]),
})
<input formControlName="number"/>
<mat-slider formControlName="number"/>
<app-customer-number formControlName="number"/>
Damit die korrekte Verbindung zur FormGroup hergestellt werden kann und Validatoren den Status des Formulars basierend auf unserer Komponente setzen genügt ein einfaches Interface: ControlValueAccessor. Dieses Interface muss von unserer Komponente implementiert werden + unsere Komponte muss als VALUE_ACCESSOR registriert werden:
@Component({
selector: 'app-customer-number',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './customer-number.component.html',
styleUrl: './customer-number.component.scss',
providers: [
{provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => CustomerNumberComponent)},
]
})
export class CustomerNumberComponent implements ControlValueAccessor {
control = new FormControl<number | null>(null);
constructor() {
this.control.valueChanges.pipe(takeUntilDestroyed()).subscribe(v => {
this.onChange(v);
this.onTouch();
})
}
private onChange: any = () => {
};
private onTouch: any = () => {
};
registerOnChange(fn: any): void { // "fn" rufen wir bei Änderungen auf
this.onChange = fn;
}
registerOnTouched(fn: any): void { // "fn" rufen wir bei "Touch" auf
this.onTouch = fn;
}
setDisabledState(isDisabled: boolean): void { // wird aufgerufen wenn das control state sich ändert
if (isDisabled) this.control.disable();
else this.control.enable();
}
writeValue(obj: any): void { // wird aufgerufen wenn sich der control wert ändert
this.control.patchValue(obj);
}
}
Damit nicht genug. Zusätzlich könnten wir auch das Interface “Validator” implementieren und (zusätzlich zu den am Control deklarierten Validatoren) eine individuelle Prüfung vorzunehmen:
@Component({
....
providers: [
{provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => CustomerNumberVVComponent)},
{provide: NG_VALIDATORS, multi: true, useExisting: forwardRef(() => CustomerNumberVVComponent)},
]
})
export class CustomerNumberVVComponent implements ControlValueAccessor, Validator {
....
validate(control: AbstractControl): ValidationErrors | null {
const value = control.value;
return (!value || value.startsWith('600')) ? null : {'customerNumber': 'have to start with 600'}
}
Live. In Farbe. Github