Angular 21 Signal Forms: Wie die Signal-API das Formularmanagement auf architektonischer Ebene revolutioniert
Signal Forms verzichten auf die Abstraktion AbstractControl zugunsten zweier strikt typisierter Primitiven:
FieldTree<T>— ein Proxy-Objekt, das den Formularbaum als Signale darstellt. Jeder Knoten ist einSignal<FieldState<T>>, und der Zugriff auf den Wert erfolgt über einen()-Aufruf.FieldState<T>— der Zustand eines bestimmten Feldes: Wert, Status (valid,touched,dirty), Fehler sowie Verwaltungsmethoden (set,reset,markAsTouched).
Dies eliminiert die Notwendigkeit, Eingabedaten manuell mit dem internen Formularzustand zu synchronisieren. In reaktiven Formularen benötigte man valueChanges plus patchValue, was das Risiko einer Desynchronisation und eine komplizierte Lebenszyklusverwaltung mit sich brachte. In Signal Forms wird das Modell direkt zur einzigen Wahrheitsquelle:
export interface LoginFormModel {
email: string;
password: string;
}
@Component({
imports: [Field],
template: `
<form>
Email: <input [field]="loginForm.email">
Password: <input [field]="loginForm.password">
</form>
`
})
export class LoginForm {
login = model.required<LoginFormModel>();
loginForm = form(this.login); // Verknüpft das Model-Signal mit dem Formularbaum
}
Hier ist this.login ein WritableSignal<LoginFormModel>, und loginForm.email() gibt einen FieldState<string> zurück. Ändert man login.set({...}), werden alle verbundenen FieldState sofort aktualisiert, und umgekehrt aktualisiert die Änderung des Werts über loginForm.email().set(...) auch login().
Typisierung ohne Verlust: Von AbstractControl zu präzisen Feldtypen
Der Hauptvorteil besteht darin, die Typstruktur auf allen Ebenen beizubehalten. In reaktiven Formularen liefert get('nested.field') AbstractControl | null, wodurch Informationen über die verschachtelte Struktur verloren gehen. In Signal Forms hat der Pfad path.additionalInformation.firstName einen strikten Typ von FieldState<string>, während path.additionalInformation() ein FieldState<{ firstName: string; lastName: string; }>. Dies ermöglicht die sichere Nutzung von effect, computed und anderen Signal-API-Primitiven ohne Typumwandlung oder Überprüfung auf undefined.
Betrachten wir ein Beispiel mit der Gruppe additionalInformation:
effect(() => {
const info = this.loginForm.additionalInformation();
console.log(info.firstName().value()); // string — der Compiler weiß genau
console.log(info.lastName().touched()); // boolean
});
Solche Typisierungen beseitigen klassische Fehler: Versuche, .value bei null aufzurufen, Fehler bei der Arbeit mit verschachtelten FormGroup sowie implizites any in Ketten wie get().get().value.
Verwaltung von Elementzuständen: hidden, disabled, readonly
Signal Forms bieten drei integrierte Mechanismen zur Steuerung von Sichtbarkeit und Interaktivität, jeweils mit eigener Semantik:
hidden(path, condition?)— entfernt das Feld vollständig aus dem DOM (via@if) und aus dem Validierungsbaum. Es beeinflussttouched/dirtynicht, da das Element im Formular gar nicht existiert.disabled(path, condition?)— macht das Feld für die Bearbeitung unzugänglich und schließt es aus der Validierung sowie aus den Zuständentouched/dirtyaus.readonly(path, condition?)— blockiert die Bearbeitung, behält das Feld jedoch im Validierungszyklus und ermöglicht die Verfolgung vontouched/dirty.
Bedingungen werden über eine Funktion definiert, die den { valueOf }-Kontext erhält. Zum Beispiel:
form<{ email: string; password: string }>(
{ email: '', password: '' },
(path) => {
disabled(path.password, ({ valueOf }) => !valueOf(path.email));
}
);```
Dies entspricht `password.disabled = !email.value`, wird jedoch reaktiv ausgeführt und gewährleistet Konsistenz.
## Debounce und Validierung: Integrierte Lösungen statt RxJS
Zur Kontrolle der Häufigkeit von Datenupdates ist die Funktion `debounce` implementiert, die entweder einen Timeout in Millisekunden oder einen benutzerdefinierten `Debouncer` akzeptiert:
debounce(path, (
{ valueOf },
abortSignal
) => {
return new Promise<void>((resolve) => {
const timeout = setTimeout(() => resolve(), valueOf(path.email).length * 100);
abortSignal.addEventListener('abort', () => clearTimeout(timeout));
});
});```
Validierung ist in synchrone (required, minLength, pattern) und asynchrone (validateAsync, validateHttp) unterteilt. Ein besonderes Merkmal ist die Unterstützung für validateTree, mit der sich regeln über mehrere Felder erstellen lassen. Zum Beispiel die Überprüfung der Passwortübereinstimmung:
validateTree(path, (ctx) => {
const { password, confirmPassword } = ctx.value();
if (password !== confirmPassword) {
return {
field: ctx.fieldTree.confirmPassword,
kind: 'passwordMismatch',
message: 'Passwörter stimmen nicht überein'
};
}
});```
Hier bietet `ctx.fieldTree` Zugriff auf alle `FieldState` innerhalb des Baums, und `ctx.value()` liefert den aktuellen Wert des gesamten Formulars.
## Was wichtig ist
- Signal Forms sind kein Ersatz für reaktive Formulare; sie stellen eine architektonische Überarbeitung unter der Signal-API dar: Das Modell und das Formular sind nun eine einzige Wahrheitsquelle.
- Die Typisierung bleibt auf allen Ebenen der Verschachtelung erhalten, wodurch `AbstractControl` und die Notwendigkeit manueller `instanceof`-Prüfungen entfallen.
- `hidden`, `disabled` und `readonly` haben klare Semantik und verwalten automatisch Validierung sowie `touched/dirty`-Zustände.
- `debounce` und `validateTree` sind in den Kern integriert, sodass für grundlegende Szenarien keine Abhängigkeit von RxJS mehr besteht.
- Der experimentelle Status bedeutet, dass sich die API noch ändern kann; ihre Verwendung in der Produktion erfordert ein klares Verständnis der Risiken und einen Migrationsplan.
Angular 21 Signal Forms markieren einen Schritt hin zu einem vorhersehbareren, typsichereren und weniger verbose Formularmanagement. Sie vereinfachen einfache Fälle nicht, reduzieren aber die Komplexität in komplexen Szenarien radikal: verschachtelte Formulare, bedingte Felder, Cross-Validation und dynamische Synchronisation. Für mittlere und senior Entwickler ist dies kein „Quick-Start“-Tool — es ist ein architektonisches Primitiv, das es wert ist, bis auf die Implementierungsebene von `FieldTree` und das Verhalten von `effect` im Formularkontext eingehend studiert zu werden.
— Editorial Team
Noch keine Kommentare.