--- name: forms-implementation description: Build reactive and template-driven forms, implement custom validators, create async validators, add cross-field validation, and generate dynamic forms for Angular applications. --- # Forms Implementation Skill ## Quick Start ### Template-Driven Forms ```typescript import { Component } from '@angular/core'; import { NgForm } from '@angular/forms'; @Component({ selector: 'app-contact', template: `
` }) export class ContactComponent { model = { name: '', email: '' }; onSubmit(form: NgForm) { if (form.valid) { console.log('Form submitted:', form.value); } } } ``` ### Reactive Forms ```typescript import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-user-form', template: ` ` }) export class UserFormComponent implements OnInit { form!: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ name: ['', [Validators.required, Validators.minLength(3)]], email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(8)]] }); } onSubmit() { if (this.form.valid) { console.log(this.form.value); } } } ``` ## Form Controls ### FormControl ```typescript // Create standalone control const nameControl = new FormControl('', Validators.required); // Get value nameControl.value // Set value nameControl.setValue('John'); nameControl.patchValue({ name: 'John' }); // Check validity nameControl.valid nameControl.invalid nameControl.errors // Listen to changes nameControl.valueChanges.subscribe(value => { console.log('Changed:', value); }); ``` ### FormGroup ```typescript const form = new FormGroup({ name: new FormControl('', Validators.required), email: new FormControl('', [Validators.required, Validators.email]), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), zip: new FormControl('') }) }); // Access nested controls form.get('address.street')?.setValue('123 Main St'); // Update multiple values form.patchValue({ name: 'John', email: 'john@example.com' }); ``` ### FormArray ```typescript const form = new FormGroup({ name: new FormControl(''), emails: new FormArray([ new FormControl(''), new FormControl('') ]) }); // Dynamic form array const emailsArray = form.get('emails') as FormArray; // Add control emailsArray.push(new FormControl('')); // Remove control emailsArray.removeAt(0); // Iterate emailsArray.controls.forEach((control, index) => { // ... }); ``` ## Validation ### Built-in Validators ```typescript import { Validators } from '@angular/forms'; new FormControl('', [ Validators.required, Validators.minLength(3), Validators.maxLength(50), Validators.pattern(/^[a-z]/i), Validators.email, Validators.min(0), Validators.max(100) ]) ``` ### Custom Validators ```typescript // Simple validator function noSpacesValidator(control: AbstractControl): ValidationErrors | null { if (control.value && control.value.includes(' ')) { return { hasSpaces: true }; } return null; } // Cross-field validator function passwordMatchValidator(group: FormGroup): ValidationErrors | null { const password = group.get('password')?.value; const confirm = group.get('confirmPassword')?.value; return password === confirm ? null : { passwordMismatch: true }; } // Usage const form = new FormGroup({ username: new FormControl('', noSpacesValidator), password: new FormControl(''), confirmPassword: new FormControl('') }, passwordMatchValidator); ``` ### Async Validators ```typescript function emailAvailableValidator(service: UserService): AsyncValidatorFn { return (control: AbstractControl): Observable