Das Verarbeiten eines Formulars in Angular ist simpel … und doch auf viele Arten möglich. Mit Reactive Forms ermöglicht uns ein <form (ngSubmit)="..."> zusammen mit einem Submit-Button, die Verarbeitung anzustoßen – oder ganz pragmatisch über ein (click) auf dem Button selbst. Bei den Signal Forms bleiben diese Möglichkeiten bestehen. Zusätzlich gibt es jedoch eine etwas mächtigere Variante : Form Submission.
Beginnen wir mit dem Template. Die Direktive formRoot erlaubt es uns, die Form Submission zu aktivieren. Dazu referenzieren wir lediglich unser Signal-Forms-Objekt.
<form [formRoot]="form">
....
<button matButton>submit</button>
</form>
Was genau passieren soll, wenn ein Formular abgeschickt wird, definieren wir nun nicht mehr in einer separaten Methode. Stattdessen ergänzen wir dies direkt bei der Erzeugung unseres Formulars als dritten Parameter – nach dem State und der Schema-Funktion.
form = form(
this.formState,
schema => {},
{
submission: {
action: async (fieldTree) => {},
onInvalid: fieldTree => {},
ignoreValidators: 'none'
}
}
);
Wie wir sehen, deklarieren wir in diesem Options-Objekt die submission, die wiederum aus bis zu drei Attributen besteht:
- action: Enthält die eigentliche Submission-Logik.
- onInvalid: Wird aufgerufen, wenn ein Submit ausgelöst wird, sich das Formular jedoch in einem invaliden State befindet (was in vielen meiner Projekte eher durch eine Deaktivierung des Submit-Buttons verhindert wird).
- ignoreValidators: Legt fest, welche Validatoren die Ausführung der action nicht verhindern sollen (all, none oder async).
Die Action beinhaltet nun unsere eigentliche Verarbeitung. Dabei darf diese am Ende ggf. Fehler zurückliefern die sich im State des Formulares niederschlagen. Dazu reicht es wenn ein Objekt (oder ein Array) von dieser Methode zurück geliefert wird das einen Typ (kind), eine optionale Nachricht (messag) und einen optionales Feld (fieldTree) deklariert. Hierüber lassen sich nun Fehlermeldungen die vom Server kommen in unser Formular integrieren:
action: async (fieldTree) => {
const loginResponse = await firstValueFrom(this.loginService.login(fieldTree.username().value(), fieldTree.password().value()));
const errors = Object.keys(loginResponse || {})
.map(key => {
return {
kind: 'validationError',
message: 'loginResponse![key]',
fieldTree: fieldTree[key as keyof typeof fieldTree] as any,
}
});
return errors;
},
Die Methode this.loginService.login liefert in meinem Beispiel im Fehlerfall ein Key-Value-Objekt mit Fehlermeldungen zurück, das anschließend auf das Formular gemappt wird. Das funktioniert natürlich nur, solange die Server-Response die passenden Keys für den Form State liefert. Alternativ kann der fieldTree natürlich auch z. B. anhand von Fehlercodes ermittelt werden, die von der Methode selbst zurückgegeben werden. Ebenso kann er ganz weggelassen werden, wenn die Fehler nicht direkt einzelnen Feldern zugeordnet werden sollen.
Die hier gezeigte Submission wird automatisch bei einem Form-Submit aufgerufen. Sie lässt sich jedoch auch manuell auslösen, um beispielsweise nur Teile eines Formulars abzusenden. Dafür genügt es, eine entsprechende Methode zu implementieren, die anschließend z. B. über einen separaten Button aufgerufen wird. Die Grundstruktur dieser Methode bleibt analog zum oben gezeigten Beispiel. Die submit-Methode erwartet als ersten Parameter ein FieldTree (Formularfeld oder Subformular). Fehler, die hierbei zurückgemeldet werden, werden entsprechend in den Form-State übernommen.
async submitUsername() {
await submit(this.form.username, async (fieldTree) => {
const response = await firstValueFrom(this.loginService.submitUsername(fieldTree().value()))
return response ? {kind: 'checkFailed', message: 'user not valid', fieldTree: fieldTree} : null
})
}
Kurz und knapp, warum also die Form Submission:
- Zentrale Deklaration der Submit-Logik direkt bei der Erstellung des Formulars. Dadurch bleibt die Formularlogik an einer Stelle gebündelt und leichter wartbar.
- Direkte Integration von Fehlern und Server-Responses in den Form-State. Rückmeldungen vom Backend können automatisch dem entsprechenden Formularzustand zugeordnet und dargestellt werden.
- Automatisches Verhindern von Submits bei ungültiger Formularvalidierung. Das Formular kann nur abgeschickt werden, wenn alle notwendigen Validierungen erfolgreich durchlaufen wurden.
- Automatisches Verhindern mehrfacher Submits während eines laufenden Submit-Vorgangs. Dadurch werden unbeabsichtigte Doppelanfragen an den Server vermieden.
- Automatisches Entfernen von erzeugten Fehlern bei erneuter Benutzerinteraktion mit den Formularfeldern. Sobald der Benutzer ein betroffenes Feld bearbeitet, kann der zugehörige Fehlerzustand automatisch aktualisiert werden.