Fortgeschritten282025-01-31

Angular Reactive Forms - Formulare professionell erstellen

Meistere Reactive Forms in Angular: Form Validation, Custom Validators, Dynamic Forms und Best Practices für robuste Formulare.

#angular#forms#reactive-forms#validation#form-builder#validators

Angular Reactive Forms

Zeit für professionelle Formulare! Reactive Forms sind die mächtigste Art, Forms in Angular zu bauen. 📝

📋 In diesem Artikel lernst du:

  • Template-Driven vs Reactive Forms
  • Reactive Forms Setup
  • FormControl, FormGroup und FormBuilder
  • Form Validation (Built-in & Custom)
  • Dynamic Forms erstellen
  • Form Arrays für mehrere Inputs
  • Cross-Field Validation
  • Async Validators
  • Best Practices für robuste Forms

Template-Driven vs Reactive Forms

Angular bietet zwei Arten von Forms:

Template-Driven Forms

Logik im Template (HTML):

<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
  <input
    type="email"
    name="email"
    [(ngModel)]="email"
    required
    email
  >
  <button [disabled]="!loginForm.valid">Login</button>
</form>

Vorteile:

  • ✅ Einfach für simple Forms
  • ✅ Wenig TypeScript Code

Nachteile:

  • ❌ Schwer zu testen
  • ❌ Wenig Kontrolle
  • ❌ Komplex bei großen Forms

Reactive Forms ⭐

Logik im TypeScript:

export class LoginComponent {
  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [Validators.required])
  });

  onSubmit() {
    if (this.loginForm.valid) {
      console.log(this.loginForm.value);
    }
  }
}
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  <input type="email" formControlName="email">
  <input type="password" formControlName="password">
  <button [disabled]="!loginForm.valid">Login</button>
</form>

Vorteile:

  • Type-safe (mit TypeScript)
  • ✅ Leicht zu testen
  • ✅ Volle Kontrolle über Form State
  • ✅ Skaliert gut für komplexe Forms
  • ✅ Dynamic Forms möglich

Nutze Reactive Forms für alles außer simpelste Forms!

Reactive Forms Setup

ReactiveFormsModule importieren

app.module.ts:

import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [
    BrowserModule,
    ReactiveFormsModule  // ✅ Import hinzufügen
  ],
  // ...
})
export class AppModule { }

Ohne diesen Import funktionieren Reactive Forms NICHT!

FormControl - Einzelnes Input Field

FormControl repräsentiert ein einzelnes Form Field.

Basic Example

login.component.ts:

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-login',
  template: `
    <div class="form-field">
      <label>Email:</label>
      <input type="email" [formControl]="emailControl">
      <p>Aktueller Wert: {{ emailControl.value }}</p>
    </div>
  `
})
export class LoginComponent {
  emailControl = new FormControl('');  // Initial Value: ''
}

Erklärung:

  • new FormControl('') - Erstellt Control mit initial value
  • [formControl]="emailControl" - Bindet Input an Control
  • emailControl.value - Aktueller Wert

FormControl mit Initial Value

emailControl = new FormControl('john@example.com');  // Prefilled

FormControl State

constructor() {
  console.log(this.emailControl.value);      // Wert
  console.log(this.emailControl.valid);      // Ist valide?
  console.log(this.emailControl.invalid);    // Ist invalide?
  console.log(this.emailControl.touched);    // Wurde angefasst?
  console.log(this.emailControl.dirty);      // Wurde geändert?
  console.log(this.emailControl.pristine);   // Noch unverändert?
}

Wert setzen/updaten

// Wert setzen
this.emailControl.setValue('new@example.com');

// Wert patchen (alternative)
this.emailControl.patchValue('new@example.com');

// Reset (zurück zu initial value)
this.emailControl.reset();

FormGroup - Mehrere Fields gruppieren

FormGroup bündelt mehrere FormControls.

Basic Login Form

login.component.ts:

import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent {

  loginForm = new FormGroup({
    email: new FormControl('', [
      Validators.required,
      Validators.email
    ]),
    password: new FormControl('', [
      Validators.required,
      Validators.minLength(6)
    ])
  });

  onSubmit() {
    if (this.loginForm.valid) {
      console.log('Form Data:', this.loginForm.value);
      // { email: 'john@example.com', password: '123456' }
    } else {
      console.log('Form is invalid!');
    }
  }

  get email() {
    return this.loginForm.get('email');
  }

  get password() {
    return this.loginForm.get('password');
  }
}

login.component.html:

<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" class="login-form">
  <h2>Login</h2>

  <!-- Email Field -->
  <div class="form-field">
    <label>Email:</label>
    <input
      type="email"
      formControlName="email"
      [class.error]="email?.invalid && email?.touched"
    >
    <div *ngIf="email?.invalid && email?.touched" class="error-message">
      <p *ngIf="email?.errors?.['required']">Email ist erforderlich</p>
      <p *ngIf="email?.errors?.['email']">Keine gültige Email-Adresse</p>
    </div>
  </div>

  <!-- Password Field -->
  <div class="form-field">
    <label>Passwort:</label>
    <input
      type="password"
      formControlName="password"
      [class.error]="password?.invalid && password?.touched"
    >
    <div *ngIf="password?.invalid && password?.touched" class="error-message">
      <p *ngIf="password?.errors?.['required']">Passwort ist erforderlich</p>
      <p *ngIf="password?.errors?.['minlength']">
        Mindestens {{ password?.errors?.['minlength'].requiredLength }} Zeichen
      </p>
    </div>
  </div>

  <!-- Submit Button -->
  <button
    type="submit"
    [disabled]="!loginForm.valid"
  >
    Login
  </button>

  <!-- Debug Info -->
  <div class="debug">
    <p>Form Valid: {{ loginForm.valid }}</p>
    <p>Form Value: {{ loginForm.value | json }}</p>
  </div>
</form>

login.component.css:

.login-form {
  max-width: 400px;
  margin: 2rem auto;
  padding: 2rem;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
}

.form-field {
  margin-bottom: 1.5rem;
}

label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 600;
}

input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: 4px;
  font-size: 1rem;
}

input.error {
  border-color: #ef4444;
}

.error-message {
  color: #ef4444;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

button {
  width: 100%;
  padding: 0.75rem;
  background: #3b82f6;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
}

button:disabled {
  background: #9ca3af;
  cursor: not-allowed;
}

.debug {
  margin-top: 1rem;
  padding: 1rem;
  background: #f3f4f6;
  border-radius: 4px;
  font-size: 0.875rem;
}

Erklärung:

  • [formGroup]="loginForm" - Bindet Form an FormGroup
  • formControlName="email" - Bindet Input an Control im FormGroup
  • loginForm.value - Alle Form Werte als Objekt
  • loginForm.valid - Ist gesamtes Form valide?
  • email?.invalid && email?.touched - Zeige Fehler nur wenn touched

Built-in Validators

Angular bietet viele Built-in Validators:

Alle Built-in Validators

import { Validators } from '@angular/forms';

new FormControl('', [
  Validators.required,              // Pflichtfeld
  Validators.email,                 // Email Format
  Validators.min(18),               // Minimum Zahl
  Validators.max(100),              // Maximum Zahl
  Validators.minLength(6),          // Mindestlänge
  Validators.maxLength(20),         // Maximallänge
  Validators.pattern(/^[a-zA-Z]+$/) // Regex Pattern
]);

Beispiele

Nur Zahlen:

ageControl = new FormControl('', [
  Validators.required,
  Validators.min(18),
  Validators.max(120)
]);

Username (alphanumerisch, 3-15 Zeichen):

usernameControl = new FormControl('', [
  Validators.required,
  Validators.minLength(3),
  Validators.maxLength(15),
  Validators.pattern(/^[a-zA-Z0-9_]+$/)
]);

Phone Number (Deutsche Handynummer):

phoneControl = new FormControl('', [
  Validators.required,
  Validators.pattern(/^(\+49|0)[1-9][0-9]{9,10}$/)
]);

Tipps & Tricks

Validator Error Messages

Error Object auslesen:

// Password Control Errors:
password?.errors
// → { required: true }  oder
// → { minlength: { requiredLength: 6, actualLength: 3 } }

Im Template nutzen:

<div *ngIf="password?.errors?.['minlength']">
  Mindestens {{ password?.errors?.['minlength'].requiredLength }} Zeichen
  (aktuell: {{ password?.errors?.['minlength'].actualLength }})
</div>

Getter für Controls

Statt:

<div *ngIf="loginForm.get('email')?.invalid">

Nutze Getter:

get email() {
  return this.loginForm.get('email');
}
<div *ngIf="email?.invalid">

Viel lesbarer!

Touched vs Dirty vs Pristine

touched: User hat Feld fokussiert (geklickt) untouched: Feld noch nie fokussiert

dirty: Wert wurde geändert pristine: Noch Original-Wert

Zeige Fehler nur wenn touched:

<div *ngIf="email?.invalid && email?.touched">
  <!-- Fehler nur nach Fokus-Verlust -->
</div>

FormBuilder - Forms schneller erstellen

FormBuilder ist ein Service der Forms einfacher macht!

Ohne FormBuilder

loginForm = new FormGroup({
  email: new FormControl('', [Validators.required, Validators.email]),
  password: new FormControl('', [Validators.required])
});

Mit FormBuilder ✅

import { FormBuilder } from '@angular/forms';

constructor(private fb: FormBuilder) {}

loginForm = this.fb.group({
  email: ['', [Validators.required, Validators.email]],
  password: ['', [Validators.required]]
});

Viel kürzer und lesbarer!

Komplexes Beispiel: Registration Form

import { Component } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html'
})
export class RegisterComponent {

  registerForm = this.fb.group({
    // Personal Info
    firstName: ['', [Validators.required, Validators.minLength(2)]],
    lastName: ['', [Validators.required, Validators.minLength(2)]],
    email: ['', [Validators.required, Validators.email]],

    // Password
    password: ['', [
      Validators.required,
      Validators.minLength(8),
      Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)  // Min 1 lowercase, 1 uppercase, 1 digit
    ]],
    confirmPassword: ['', [Validators.required]],

    // Address
    address: this.fb.group({
      street: ['', Validators.required],
      city: ['', Validators.required],
      zip: ['', [Validators.required, Validators.pattern(/^\d{5}$/)]],
      country: ['DE', Validators.required]
    }),

    // Terms
    agreeToTerms: [false, Validators.requiredTrue]
  });

  constructor(private fb: FormBuilder) {}

  onSubmit() {
    if (this.registerForm.valid) {
      console.log('Registration Data:', this.registerForm.value);
    }
  }

  // Getter
  get firstName() { return this.registerForm.get('firstName'); }
  get lastName() { return this.registerForm.get('lastName'); }
  get email() { return this.registerForm.get('email'); }
  get password() { return this.registerForm.get('password'); }
  get confirmPassword() { return this.registerForm.get('confirmPassword'); }
  get address() { return this.registerForm.get('address'); }
  get agreeToTerms() { return this.registerForm.get('agreeToTerms'); }
}

Nested FormGroup: address ist ein eigenes FormGroup!

Custom Validators

Erstelle eigene Custom Validators für spezielle Validierungen!

Simple Custom Validator

validators/custom-validators.ts:

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export class CustomValidators {

  // Nur Buchstaben (keine Zahlen/Sonderzeichen)
  static onlyLetters(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      const value = control.value;

      if (!value) {
        return null;  // Leer ist OK (nutze Validators.required für Pflichtfeld)
      }

      const valid = /^[a-zA-ZäöüÄÖÜß\s]+$/.test(value);

      return valid ? null : { onlyLetters: true };
    };
  }

  // Keine Leerzeichen erlaubt
  static noWhitespace(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      const value = control.value;

      if (!value) {
        return null;
      }

      const hasWhitespace = /\s/.test(value);

      return hasWhitespace ? { noWhitespace: true } : null;
    };
  }

  // Starkes Passwort (min 8 chars, 1 uppercase, 1 lowercase, 1 digit, 1 special char)
  static strongPassword(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      const value = control.value;

      if (!value) {
        return null;
      }

      const hasUpperCase = /[A-Z]/.test(value);
      const hasLowerCase = /[a-z]/.test(value);
      const hasDigit = /\d/.test(value);
      const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value);
      const isLongEnough = value.length >= 8;

      const valid = hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar && isLongEnough;

      if (valid) {
        return null;
      }

      return {
        strongPassword: {
          hasUpperCase,
          hasLowerCase,
          hasDigit,
          hasSpecialChar,
          isLongEnough
        }
      };
    };
  }
}

Custom Validator nutzen

import { CustomValidators } from './validators/custom-validators';

registerForm = this.fb.group({
  firstName: ['', [
    Validators.required,
    CustomValidators.onlyLetters()  // ✅ Custom Validator
  ]],
  username: ['', [
    Validators.required,
    CustomValidators.noWhitespace()
  ]],
  password: ['', [
    Validators.required,
    CustomValidators.strongPassword()
  ]]
});

Error Messages:

<div *ngIf="firstName?.errors?.['onlyLetters']">
  Nur Buchstaben erlaubt
</div>

<div *ngIf="password?.errors?.['strongPassword'] as error">
  <p>Passwort muss enthalten:</p>
  <ul>
    <li [class.valid]="error.hasUpperCase">✓ Großbuchstabe</li>
    <li [class.valid]="error.hasLowerCase">✓ Kleinbuchstabe</li>
    <li [class.valid]="error.hasDigit">✓ Zahl</li>
    <li [class.valid]="error.hasSpecialChar">✓ Sonderzeichen</li>
    <li [class.valid]="error.isLongEnough">✓ Min. 8 Zeichen</li>
  </ul>
</div>

Cross-Field Validation

Cross-Field Validators validieren mehrere Fields zusammen.

Beispiel: "Password" und "Confirm Password" müssen identisch sein.

Password Match Validator

validators/password-match.validator.ts:

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function passwordMatchValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const password = control.get('password');
    const confirmPassword = control.get('confirmPassword');

    if (!password || !confirmPassword) {
      return null;
    }

    // Passwörter stimmen nicht überein
    if (password.value !== confirmPassword.value) {
      confirmPassword.setErrors({ passwordMismatch: true });
      return { passwordMismatch: true };
    }

    // Passwörter stimmen überein → Error entfernen
    const errors = confirmPassword.errors;
    if (errors) {
      delete errors['passwordMismatch'];
      confirmPassword.setErrors(Object.keys(errors).length > 0 ? errors : null);
    }

    return null;
  };
}

Validator auf FormGroup anwenden

registerForm = this.fb.group({
  password: ['', [Validators.required, Validators.minLength(8)]],
  confirmPassword: ['', [Validators.required]]
}, {
  validators: [passwordMatchValidator()]  // ✅ Form-Level Validator
});

Error Message:

<div *ngIf="confirmPassword?.errors?.['passwordMismatch']">
  Passwörter stimmen nicht überein
</div>

Häufige Fehler

Fehler 1: ReactiveFormsModule nicht importiert

Problem:

Error: Can't bind to 'formGroup' since it isn't a known property of 'form'

Lösung:

// app.module.ts
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [ReactiveFormsModule]  // ✅ Import hinzufügen
})

Fehler 2: formControlName ohne formGroup

Problem:

<form>
  <input formControlName="email">  <!-- ❌ Fehlt [formGroup] -->
</form>

Lösung:

<form [formGroup]="loginForm">  <!-- ✅ formGroup hinzufügen -->
  <input formControlName="email">
</form>

Fehler 3: setValue vs patchValue

Problem:

// Form hat 3 Fields: email, password, rememberMe
this.loginForm.setValue({ email: 'test@test.com' });
// ❌ Error! setValue braucht ALLE Fields!

Lösung 1: Alle Werte

this.loginForm.setValue({
  email: 'test@test.com',
  password: '',
  rememberMe: false
});

Lösung 2: patchValue (partial)

this.loginForm.patchValue({
  email: 'test@test.com'  // ✅ Andere Fields bleiben unverändert
});

Fehler 4: Fehler zu früh anzeigen

Problem:

<!-- Fehler sofort sichtbar, auch wenn User noch gar nicht getippt hat -->
<div *ngIf="email?.invalid">Error!</div>

Lösung:

<!-- Fehler nur nach Touch -->
<div *ngIf="email?.invalid && email?.touched">Error!</div>

<!-- Oder nach Submit Versuch -->
<div *ngIf="email?.invalid && (email?.touched || submitted)">Error!</div>

Dynamic Forms

Dynamische Forms ändern sich basierend auf User-Input!

Beispiel: Conditional Fields

export class DynamicFormComponent implements OnInit {
  form = this.fb.group({
    accountType: ['personal', Validators.required],
    firstName: ['', Validators.required],
    lastName: ['', Validators.required],

    // Company Fields (nur wenn accountType = 'business')
    companyName: [''],
    vatNumber: ['']
  });

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    // ✅ Watch accountType changes
    this.form.get('accountType')?.valueChanges.subscribe(type => {
      const companyName = this.form.get('companyName');
      const vatNumber = this.form.get('vatNumber');

      if (type === 'business') {
        // Business Account → Company Fields required
        companyName?.setValidators([Validators.required]);
        vatNumber?.setValidators([Validators.required]);
      } else {
        // Personal Account → Company Fields optional
        companyName?.clearValidators();
        vatNumber?.clearValidators();
      }

      // ✅ Validators updaten
      companyName?.updateValueAndValidity();
      vatNumber?.updateValueAndValidity();
    });
  }

  get isBusinessAccount() {
    return this.form.get('accountType')?.value === 'business';
  }
}

Template:

<form [formGroup]="form">
  <select formControlName="accountType">
    <option value="personal">Privat</option>
    <option value="business">Geschäftlich</option>
  </select>

  <input formControlName="firstName" placeholder="Vorname">
  <input formControlName="lastName" placeholder="Nachname">

  <!-- ✅ Conditional Fields -->
  <div *ngIf="isBusinessAccount">
    <input formControlName="companyName" placeholder="Firmenname">
    <input formControlName="vatNumber" placeholder="USt-IdNr.">
  </div>
</form>

FormArray - Dynamische Liste von Controls

FormArray für mehrere gleichartige Inputs (z.B. mehrere Email-Adressen).

Beispiel: Multiple Email Addresses

import { Component } from '@angular/core';
import { FormBuilder, FormArray, Validators } from '@angular/forms';

@Component({
  selector: 'app-emails',
  template: `
    <form [formGroup]="form">
      <h3>Email-Adressen</h3>

      <div formArrayName="emails">
        <div *ngFor="let email of emails.controls; let i = index" class="email-row">
          <input
            [formControlName]="i"
            placeholder="Email {{ i + 1 }}"
          >
          <button type="button" (click)="removeEmail(i)">Entfernen</button>
        </div>
      </div>

      <button type="button" (click)="addEmail()">+ Email hinzufügen</button>

      <div class="debug">
        <p>Emails: {{ emails.value | json }}</p>
      </div>
    </form>
  `
})
export class EmailsComponent {
  form = this.fb.group({
    emails: this.fb.array([
      this.fb.control('', [Validators.required, Validators.email])
    ])
  });

  constructor(private fb: FormBuilder) {}

  get emails() {
    return this.form.get('emails') as FormArray;
  }

  addEmail() {
    this.emails.push(
      this.fb.control('', [Validators.required, Validators.email])
    );
  }

  removeEmail(index: number) {
    this.emails.removeAt(index);
  }
}

Erklärung:

  • FormArray - Liste von Controls
  • formArrayName="emails" - Bindet Array
  • [formControlName]="i" - Bindet Control per Index
  • emails.push(...) - Neues Control hinzufügen
  • emails.removeAt(i) - Control entfernen
🔍

Debugging & Troubleshooting

Form Value debuggen

Im Template:

<pre>{{ form.value | json }}</pre>
<pre>{{ form.status }}</pre>

Im TypeScript:

constructor() {
  // ✅ Watch alle Changes
  this.form.valueChanges.subscribe(value => {
    console.log('Form Value:', value);
  });

  // ✅ Watch Status Changes
  this.form.statusChanges.subscribe(status => {
    console.log('Form Status:', status);  // VALID, INVALID, PENDING
  });

  // ✅ Watch einzelnes Field
  this.form.get('email')?.valueChanges.subscribe(value => {
    console.log('Email changed:', value);
  });
}

Validation Errors finden

onSubmit() {
  if (this.form.invalid) {
    console.log('Form is invalid!');

    // ✅ Alle Errors loggen
    Object.keys(this.form.controls).forEach(key => {
      const control = this.form.get(key);
      if (control?.invalid) {
        console.log(`${key} errors:`, control.errors);
      }
    });
  }
}

FormArray Index Probleme

Problem: [formControlName]="i" funktioniert nicht

Check:

  1. Hast du formArrayName="emails" am Parent?
  2. Nutzt du let i = index in *ngFor?
  3. Ist [formControlName] mit eckigen Klammern (Property Binding)?
<!-- ✅ CORRECT -->
<div formArrayName="emails">
  <div *ngFor="let control of emails.controls; let i = index">
    <input [formControlName]="i">
  </div>
</div>

Async Validators

Async Validators für Validierungen die API-Calls brauchen.

Beispiel: Prüfe ob Username bereits existiert.

Username Availability Validator

validators/username-async.validator.ts:

import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, delay } from 'rxjs/operators';

export function usernameAvailableValidator(): AsyncValidatorFn {
  return (control: AbstractControl): Observable<ValidationErrors | null> => {
    const username = control.value;

    if (!username) {
      return of(null);
    }

    // Simuliere API Call (in Realität: HttpClient)
    const takenUsernames = ['admin', 'john', 'test', 'user'];
    const isTaken = takenUsernames.includes(username.toLowerCase());

    return of(isTaken).pipe(
      delay(1000),  // Simuliere Netzwerk-Delay
      map(taken => taken ? { usernameTaken: true } : null)
    );
  };
}

Async Validator nutzen

form = this.fb.group({
  username: [
    '',
    [Validators.required],  // Sync Validators
    [usernameAvailableValidator()]  // Async Validators (3. Parameter!)
  ]
});

Template:

<input formControlName="username">

<!-- ✅ Pending State (während API Call) -->
<p *ngIf="username?.pending">Prüfe Verfügbarkeit...</p>

<!-- ✅ Error Message -->
<p *ngIf="username?.errors?.['usernameTaken']">
  Username ist bereits vergeben
</p>

Wichtig: Form Status ist PENDING während Async Validator läuft!

🎯

Zusammenfassung

Du hast gelernt:

  • Reactive Forms sind mächtiger als Template-Driven Forms
  • ReactiveFormsModule importieren
  • FormControl für einzelne Fields
  • FormGroup um mehrere Controls zu gruppieren
  • FormBuilder macht Code kürzer
  • Built-in Validators: required, email, minLength, pattern, etc.
  • Custom Validators für eigene Validierungslogik
  • Cross-Field Validators für Validierung über mehrere Fields
  • FormArray für dynamische Listen
  • Async Validators für API-basierte Validierung
  • touched & dirty für bessere UX bei Fehlern

Key Takeaways:

  • Reactive Forms = Type-safe, testbar, skalierbar
  • FormBuilder für cleanen Code
  • Zeige Errors nur nach touched
  • Custom Validators für komplexe Logik
  • valueChanges Observable für Reactive Updates

Form Setup Pattern:

form = this.fb.group({
  email: ['', [Validators.required, Validators.email]],
  password: ['', [Validators.required, Validators.minLength(8)]]
});

get email() { return this.form.get('email'); }

onSubmit() {
  if (this.form.valid) {
    console.log(this.form.value);
  }
}

Reactive Forms sind der Standard für professionelle Angular Apps! 🎯

Quiz

📝 Quiz

Was ist der Hauptvorteil von Reactive Forms?

📝 Quiz

Wofür nutzt man FormBuilder?

📝 Quiz

Wann sollte man Fehler anzeigen?

Übungen

Übung 1: Contact Form

Erstelle ein Kontaktformular mit:

  • Name (required, min 2 chars)
  • Email (required, valid email)
  • Subject (required, dropdown: "Support", "Sales", "Other")
  • Message (required, min 10 chars, max 500 chars)
  • Subscribe to Newsletter (checkbox)

Zeige alle Errors nur nach touched. Disable Submit Button bis Form valid.

Übung 2: Registration Form mit Password Match

Erstelle Registration Form mit:

  • Username (required, 3-20 chars, no whitespace)
  • Email (required, valid email)
  • Password (required, min 8 chars, strong password validator)
  • Confirm Password (required, must match password)
  • Terms Checkbox (required)

Implementiere Custom Validator für Password Match.

Übung 3: Dynamic Skill Form

Erstelle Form wo User mehrere Skills hinzufügen kann:

  • Jeder Skill hat: Name (text) + Level (1-5 stars)
  • "+ Skill hinzufügen" Button
  • "Entfernen" Button pro Skill
  • Mindestens 1 Skill erforderlich

Nutze FormArray!

Nächste Schritte

Im nächsten Artikel:

  • HTTP Client
  • API Requests (GET, POST, PUT, DELETE)
  • Error Handling
  • Interceptors
  • RxJS Operators für HTTP

➡️ Weiter zu: Angular HTTP & APIs

Forms gemeistert! Zeit für echte Daten von APIs! 🚀

AngularLektion 8 von 10
80% abgeschlossen
Lektion abgeschlossen!

Gut gemacht! 🎉

Du hast "Angular Reactive Forms - Formulare professionell erstellen" abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten