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

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert