Überblick: Model-Driven Forms
Angular 2+ bietet mit dem Konzept der Model-Driven Forms (Reactive Forms) einen Ansatz, der - im Gegensatz zur Nutzung von Template-Driven Forms - bei der Gestaltung von Web-Formularen sehr viele Freiheiten der Gestaltung lässt, die Formularlogik im TypeScript-Code kapselt und sie somit weitestgehend vom HTML-Code entkoppelt.
Das zentrale Konstrukt bei diesem Ansatz ist ein FormGroup-Objekt, welches man entsprechend der Formularstruktur mit weiteren Kontrollelementen, -gruppen oder arrays anreichern kann. Dabei kann man den Aufbau exakt an der Benutzeroberfläche und der benötigten Formular- und Programmlogik ausrichten, ganz unabhängig von der Datenhaltung. Möchte man beispielsweise das Benutzerprofil eines Mitarbeiters im Browser darstellen, so wird im zu verarbeitenden Benutzerdatensatz in der Regel eine Mitarbeiternummer oder ein ähnlicher Schlüsselwert hinterlegt sein, den man für den konkreten Einsatzzweck im Frontend nicht benötigt. Auch kann der Benutzerdatensatz Lücken enthalten, d. h. es fehlen möglicherweise Daten, die im Frontend angezeigt werden könnten.
Werte in das Formular übertragen
Je größer ein solcher Datensatz und das zugehörige Formular werden, desto umständlicher wäre es, die Daten einzeln in die FormGroup zu übertragen. Dafür stellt Angular 2 in der FormGroup-Klasse die Methoden setValue
und patchValue
zur Verfügung. Für setValue
müssen Formular- und Datenstruktur exakt übereinstimmen, was wie oben angesprochen häufig nicht der Fall ist. Haben Formular- und Datenstruktur jedoch nur grundsätzlich den gleichen Aufbau (abgesehen von überflüssigen oder fehlenden Werten), so lassen sich die Daten ganz einfach mit patchValue
in das Formular übertragen:
Beispieldaten
profileData = {
'personID': '123456',
'name': 'Hans Mustermann',
'userGroups': ['Mitarbeiter', 'Betriebsrat'],
'address': {
'city': 'Berlin'
}
}
Beispielformular (mit FormBuilder erstellt)
this.profileForm = this.builder.group({
userData: this.builder.group({
name: [''],
userGroups: this.builder.array(['', '']),
address: this.builder.group({
street: [''],
city: ['']
})
})
})
Länge des FormArray anpassen, Daten ins Formular übertragen
this.adjustUserGroupsArray();
this.profileForm.patchValue(this.profileData);
Unsere Profildaten enthalten also eine ID, die nicht im Formular verwendet wird, und es fehlt die Angabe eine Straße in der Adresse. Dies macht patchValue
jedoch nichts aus.
Gefahrenquellen
Etwas aufpassen muss man lediglich bei der Verwendung von Arrays: Bevor man patchen kann, muss man selbst dafür sorgen, dass im FormArray die gleiche Anzahl an Einträgen zur Verfügung steht, wie man aus dem Datenarray bekommt. Dies passiert im obigen Beispiel in der vorgeschalteten Funktion adjustUserGroupsArray
. Der userGroups-Eintrag dürfte sogar (wie der street-Eintrag) in unserem Datensatz fehlen, ohne dass patchValue
damit Probleme hätte.
Problematisch wird es jedoch, wenn man ein undefiniertes Array übergibt:
profileData = {
'personID': '123456',
'name': undefined,
'userGroups': undefined,
'address': undefined
}
Während das FormControl name
und das FormControl group
beim Patchen mit diesem neuen Datenobjekt keine Probleme haben, wird wegen des undefinierten Arrays der Fehler
ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'forEach' of undefined
ausgegeben, da beim Übertragen der Werte der Arrayname gefunden wird, das Array anschließend jedoch nicht mehr durchlaufen werden kann. Dieser Fehler ist gerade bei großen Datensätzen nicht auf den ersten Blick erkennbar. Daher sollte man immer darauf achten, nicht unnötigerweise undefinierte Einträge zu erzeugen, sondern die Datensätze beim Datentransfer sauber übernehmen.