Dynamische Formulare mit Angular Reactive Forms

Dynamische Formulare mit Angular

Dynamische Formulare mit Angular Reactive Forms

Formulare sind essentielle Bestandteile jeder Webapplikation.

Zu beachten ist, dass der Aufwand des Aufbaus größere, mehrseitige statische Formulare und mehr Zeit in Anspruch nimmt. Ein weitere Nachteil ist, dass der Quellcode geändert werden muss, sobald der Benutzer weitere Daten angeben soll.

Mit Hilfe von Angular hat man durch die Reactive Forms die Möglichkeit Formulare dynamisch aufzubauen um so dieses Problem mit geringem Zeitaufwand lösen zu können.

In meinem Beispiel werde ich euch das veranschaulichen.

Den gesamten Quellcode findet ihr auf Stackblitz.

Laden der Daten

Die Daten der Formulare werden idealerweise in einer Datenbank gespeichert und mithilfe von unterschiedlichen Möglichkeiten (z.B. einer Rest-Schnittstelle) zu Verfügung gestellt.

In meinem Beispiel habe ich eine Klasse definiert, welche die Grundstruktur eines möglichen Formulars darstellt.

 Mit Hilfe des Service  ‘FakeFormService’  werden die vordefinierte Daten von einem JSON-File konsumiert.

fake-form.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormModel } from '../model/form.model';

@Injectable({
   providedIn: 'root'
})
export class FakeFormService {

  constructor(private http: HttpClient) {}

  // Fetch Functions
  getRegistrationForm(): Promise<FormModel[]> {
    return this.http.get<FormModel[]>("assets/form-data.json").toPromise();
  }
}

Aufbau des Formulares

Sobald die Daten vorhanden sind, müssen diese auf die Struktur von Reactive Forms umgewandelt werden. Da kommen die von Angular vordefinierten Klassen FormGroup und FormControl ins Spiel.

Mit Hilfe der Funktion ‘initForm()’ in der FormUtils wird eine neue FormGroup initialisiert. 

Danach werden alle Elemente unseres Arrays in eine FormControl umgewandelt und zur FormGroup hinzugefügt.

form.utils.ts

import { FormGroup, FormControl, Validators } from '@angular/forms';
import { FormModel } from '../model/form.model';

export class FormUtils {

  static initForm(formDatas: FormModel[]): FormGroup {
    const formGroup = new FormGroup({});

    for (const formData of formDatas ) {
      const formControl = new FormControl();
      if (formData.required) {
        formControl.setValidators(Validators.required);
      }

      formGroup.addControl(formData.field, formControl);
    }

    return formGroup;
  }
}

Anzeige der Input-Felder

Unsere Form ist bereits initialisiert. Um Daten erfassen zu können, ist es nützlich, dass der User auch die Möglichkeit hat dieses Feld zu befüllen.

Um die unterschiedlichen Eingabefelder korrekt anzeigen zu können werden wir unsere ursprünglichen Daten zur Hilfe holen.

Um Angular wissen zu lassen, wo sich unser Formular befindet, müssen wir die ‘formGroup’ Direktive verwenden. Dies ist in der  ‘app.component.html’ Zeile 3 zu finden.

Angular bietet die Möglichkeit in einem HTML File Arrays zu durchlaufen. Für jedes Element, abhängig dessen Typs, wird in der Oberfläche das entsprechende Input-Feld angezeigt. In meinem Beispiel habe ich ‘TEXT’, ‘EMAIL’, ‘NUMBER’ und ‘RADIOBUTTON’  verwendet. Selbstverständlich besteht auch die Möglichkeit andere Typen zu definieren.

Hierbei dürfen wir ja nicht vergessen, unsere Input-Felder mit den einzelnen FormController zu verknüpfen. Dabei hilft uns die ‘formControl’ Direktive von Angular, wie im ‘app.component.html’ zu sehen ist.

app.component.html

<div 
class="form-container"
[formGroup]="registrationForm" 
*ngIf="registrationForm">
  <ng-container *ngFor="let data of formDatas">
    <ng-container [ngSwitch]="data.type">
      <b for="data.field">{{ data.field }}</b>
      <input *ngSwitchCase="'TEXT'" type="text" [formControlName]="data.field" />
      <input *ngSwitchCase="'NUMBER'" type="number" [formControlName]="data.field" />
      <input *ngSwitchCase="'EMAIL'" type="email" [formControlName]="data.field" />
      <ng-container *ngSwitchCase="'RADIO'">
        <div class="radio-options">
          <div *ngFor="let option of data.options">
            <input 
            type="radio" 
            [name]="data.field" 
            [formControlName]="data.field" 
            [value]="option.value">
            <span>{{ option.label }}</span>
          </div>
        </div>
      </ng-container>
    </ng-container>
  </ng-container>
</div>
<pre *ngIf="registrationForm">{{ registrationForm?.value | json }}</pre>

Voila! Unser Formular ist fertig und kann ausgefüllt werden.

Konklusion

Wie ihr im Beispiel sehen könnt, hat man dutch Angular die Möglichkeit mit wenig Aufwand dynamische Formulare zu erstellen. Falls der Benutzer Anpassungen an den Formular vornehmen möchte, müssen nur die gewünschten Datenfelder in der Datenbank ergänzt werden.

Geza Tömböly

11.08.2020