GEDOPLAN
AngularWebprogrammierung

Angular Formulare, Reactive zu Signals (1/2)

AngularWebprogrammierung
signalForms1 png

Die Welt von Angular dreht sich aktuell um Signals. Seit einigen Jahren halten sie nach und nach Einzug in die verschiedenen Bereiche von Angular und sollen zum einen das Framework unterstützen, das Rendering zu optimieren und auf der anderen Seite uns Entwicklern helfen, auf State-Änderungen zu reagieren. Der nächste große Schritt: Signal Forms (mit Angular 21 noch „experimental“!)

Model definieren (reactive)

fb = inject(FormBuilder);

registerForm = this.fb.group({
    mail: [''],
    password: [''],
    passwordRepeat: [''],
    confirmed: [false],
    newsletter: [false]
})

Über den FormBuilder (alternative über die direkte Instanziierung von FormGroup und FormControl) definieren wir die Struktur unseres Formulars als einfaches Objekt. Default Werte werden hier direkt zugewiesen.

Model definieren (signal)

registerFormData = signal({
    mail: '',
    password: '',
    passwordRepeat: '',
    confirmed: false,
    newsletter: false
})

registerForm = form(this.registerFormData);

Wir beginnen mit einem Signal, das den State unseres Formulars darstellt. Diesen übergeben wir der „form“ Methode, die daraus entsprechend Signal-basierte Controls macht. Form und Signal werden bei Änderungen synchron gehalten.

Model Binding (reactive)

<form [formGroup]="registerForm"> 
  <mat-form-field> 
    <label>Mail</label> 
    <input formControlName="mail" matInput/>
    <mat-error>{{ registerForm.controls.mail.errors | errorMessages }}</mat-error>
  </mat-form-field>

<button mat-flat-button [disabled]="registerForm.invalid">submit</button>

Unsere FormGroup verknüpfen wir z.B. mit einem Formular (formGroup). Das Binding zu einem einzelnen Feld erfolgt über die Direktive „formControlName“. Zugriff auf den Status des Controls (touched, dirty, errors…) erhalten wir über unser Formularobjekt „registerForm.controls.[controlName]

Model Binding (signal)

<form>
 <mat-form-field>
   <label>Mail</label>
   <input [formField]="registerForm.mail" matInput/>
   <mat-error>{{ registerForm.mail().errors() | errorMessages }}</mat-error>
 </mat-form-field>

<button mat-flat-button [disabled]="registerForm().invalid()">submit</button>

Für die Signals-Forms benötigen wir lediglich die Direktive „formField„, die unsere UI an den entsprechenden Form-Tree bindet. Der Zugriff auf die Elemente unseres Formulars und deren Status erfolgt (natürlich) via Signal-Properties, hier z.B. um auf die Fehler zugreifen zu können „registerForm.mail().errors()“ oder den globalen invalid-State des Formulars „registerForm().invalid()

Validierung (reactive)

registerForm = this.fb.group({
 mail: ['', [Validators.required, Validators.email]],
 //...
}, {
 validators: [
   (form) => {
      const controls = (form as FormGroup).controls;
      if (form.value.password !== form.value.passwordRepeat) {
          controls["passwordRepeat"].setErrors({passwordsDontMatch: true})
      } else {
          controls["passwordRepeat"].setErrors(null)
      }
      return null;
   }
 ]
})

Validatoren werden direkt bei der Definition der Formularstruktur definiert. Globale / Feld-übergreifende Validatoren finden ihren Platz in den FormOptions der Gruppe.

Validierung (signal)

registerForm = form(this.registerFormData, (schema) => {
  required(schema.mail);
  email(schema.mail);
  
  validate(schema.passwordRepeat, (context) => {
      if (context.valueOf(schema.password) !== context.value()) {
          return {kind: 'passwordsDontMatch'}
      }
      return null;
  });

Hier werden die Validatoren separat in einer Callbackmethode definiert, indem entsprechende Validator-Methoden mit dem Pfad des Feldes aufgerufen werden. Das ist meiner Meinung nach tatsächlich ein negativer Aspekt, da hier viel von der Lesbarkeit bei großen Formularen eingebüßt wird. Dafür gibt es nun eine elegantere Lösung für Crossfield-Validatoren. Anstatt untypisiert auf die Formular-Felder zugreifen zu müssen, erlaubt uns das Schema über „context.valueOf(schema.password)“ einen typsicheren Zugriff auf andere Felder. Auch das manuelle Setzen des Feld-spezifischen Errors entfällt hier, da die Feld-Zuordnung direkt über den Pfad angegeben werden kann. Das funktioniert nur, dank den zugrunde liegenden Signals. Diese sorgen dafür, dass bei Änderungen aller verwendeten Felder (und nur dann, nicht, wie oben, bei jeder Änderung in der Formular-Gruppe) durchgeführt (in diesem Fall: schema.password und context.value() (= schema.passwordRepeat)).

Mehrwert

Bei den Crossfield Validatoren haben wir schon echte Vorteile aufgespürt (bessere Typisierung, dediziertere Validierung-Durchläufe). Aber es gibt noch mehr. Im zweiten Teil dieses Artikels werfen wir einen detaillierteren Blick auf die Vorteile der Signal Forms.

Schreibe einen Kommentar

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

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!