Obwohl das Formular aus der letzten Lektion fehlerfrei funktioniert, kann es verbessert werden. Die Typensicherheit lässt noch Wünsche offen. Bisher existieren die Werte für den Vornamen und den Nachnamen als unabhängige und eigenständige string-Datentypen. Besser wäre es beide in einem gemeinsamen Typen zusammenzufassen.Dieser Typ soll Person heißen. Weil Person keine Methoden enthalten soll, genügt ein Interface statt einer Klasse.
export interface Person { firstName: string; lastName: string; }
Leider ist dieses Interface ausschließlich für das Datenmodell. Die Bezeichner und Typen im Formular sind davon nicht betroffen und benötigen für zusätzliche Typensicherheit ein eigenes Interface. Aber es gibt einen Weg, ein Interface für das Formular aus dem Interface des Datenmodells zu erzeugen.
type ToFormControls = { [K in keyof T]: FormControl ; }; export type PersonForm = ToFormControls ;
Die im Listing gezeigte Funktion ToFormControls erzeugt aus den Namen und Typen eines Datenmodells, ein passendes Interface für eine FormGroup. PersonForm ist die Implementierung von diesem Interface.
Jetzt ist es ein leichtes, dieses Interface bei der Erzeugung der FormGroup einzusetzen. Diese Vorgehensweise hat Vorteile. Wird das Datenmodel verändert, werden beispielsweise Eigenschaften hinzugefügt oder Bezeichner umbenannt, führt dies umgehend zu einem Fehler. Änderungen am Datenmodell führen zu einer automatischen Änderung am Interface für die FormGroup. Die Implementierung muss angepasst werden, um den Fehler zu beheben.
readonly personForm = new FormGroup ({ firstName: new FormControl('', {validators: [Validators.required, Validators.minLength(2)]}), lastName: new FormControl('', {validators: [Validators.required, Validators.minLength(2)]}) })
Die onSubmit-Methode kann ebenfalls angepasst werden, denn mit einem Datennmodel ist es möglich, die Werte des Formulars in eine Person zu konvertieren. Vorname und Nachname sollten nicht unabhängig existieren, sondern immer als Eigenschaften einer Person.
// Methode wird aufgerufen, wenn das Formular gültig abgesendet wurde onSubmit(): void { if (!this.personForm.valid) { return; } const person: Person = { firstName: this.personForm.controls.firstName.value!, lastName: this.personForm.controls.lastName.value! }; console.log('Firstname:', person.firstName); console.log('Lastname:', person.lastName); this.personForm.reset(); }
Das Interface für die FormGroup automatisch zu erzeugen, ist ein guter Schritt für die Typensicherheit. Jedoch hat der gezeigte Ansatz einen Schönheitsfehler. Aktuell werden alle Eigenschaften von Person zu einem FormControl konvertiert. In der Praxis gibt es aber oft Eigenschaften, die nicht über ein Formular eingegeben werden sollen. Zum Beispiel eine Personalnummer. Es fehlt die Option, gezielt Eigenschaften bei der Konvertierung auszuschließen. Mit kleinen Änderungen an der ToFormControls-Funktion wird das möglich.
export interface Person { firstName: string; lastName: string; internalId?: string; // Soll nicht in das Formular isAdmin?: boolean; // Soll nicht in das Formular } type ToFormControls = { [K in Exclude ]: FormControl ; }; export type PersonForm = ToFormControls ;
Hier der gesamte Typescript-Code im Zusammenhang:
import { Component } from '@angular/core'; import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; import { CommonModule } from '@angular/common'; import { ReactiveFormsModule } from '@angular/forms'; @Component({ selector: 'app-person-form', standalone: true, imports: [CommonModule, ReactiveFormsModule], templateUrl: './person-form.component.html', styleUrl: './person-form.component.scss' }) export class PersonFormComponent { constructor(private fb: FormBuilder) { } readonly personForm = new FormGroup ({ firstName: new FormControl('', {validators: [Validators.required, Validators.minLength(2)]}), lastName: new FormControl('', {validators: [Validators.required, Validators.minLength(2)]}) }) // Methode wird aufgerufen, wenn das Formular gültig abgesendet wurde onSubmit(): void { if (!this.personForm.valid) { return; } const person: Person = { firstName: this.personForm.controls.firstName.value!, lastName: this.personForm.controls.lastName.value! }; console.log('Firstname:', person.firstName); console.log('Lastname:', person.lastName); this.personForm.reset(); } } export interface Person { firstName: string; lastName: string; internalId?: string; // Soll nicht in das Formular isAdmin?: boolean; // Soll nicht in das Formular } type ToFormControls = { [K in Exclude ]: FormControl ; }; export type PersonForm = ToFormControls ;