angular-forms

v2026.09.24

Angular Reactive Forms with validators, FormArray, and dynamic forms. Covers typed forms, custom validators, and form patterns. USE WHEN: user mentions "Angular forms", "reactive forms", "FormGroup", "FormArray", "validators", "form validation", "Angular form patterns" DO NOT USE FOR: React forms - use `react-hook-form` or `react-forms`, template-driven forms for simple cases

GitHub
Install command
npx skhub add claude-dev-suite/angular-forms
Markdown
SKILL.md

Angular Forms - Quick Reference

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: angular, topic: forms for comprehensive documentation.

Typed Reactive Forms (Angular 14+)

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

@Component({
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <input formControlName="name" />
      <input formControlName="email" type="email" />
      <button type="submit" [disabled]="form.invalid">Save</button>
    </form>
  `
})
export class UserFormComponent {
  private fb = inject(FormBuilder);

  form = this.fb.nonNullable.group({
    name: ['', [Validators.required, Validators.minLength(2)]],
    email: ['', [Validators.required, Validators.email]],
    age: [0, [Validators.min(0), Validators.max(150)]],
  });

  onSubmit() {
    if (this.form.valid) {
      const value = this.form.getRawValue(); // Typed!
      console.log(value.name); // string, not string | null
    }
  }
}

FormArray

form = this.fb.group({
  name: ['', Validators.required],
  addresses: this.fb.array([this.createAddress()]),
});

createAddress() {
  return this.fb.group({
    street: ['', Validators.required],
    city: ['', Validators.required],
    zip: ['', [Validators.required, Validators.pattern(/^\d{5}$/)]],
  });
}

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

addAddress() {
  this.addresses.push(this.createAddress());
}

removeAddress(index: number) {
  this.addresses.removeAt(index);
}

Custom Validators

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

// Sync validator
export function forbiddenName(name: string): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const forbidden = control.value === name;
    return forbidden ? { forbiddenName: { value: control.value } } : null;
  };
}

// Async validator
export function uniqueEmail(userService: UserService): AsyncValidatorFn {
  return (control: AbstractControl): Observable<ValidationErrors | null> => {
    return userService.checkEmail(control.value).pipe(
      map(exists => exists ? { emailTaken: true } : null),
      catchError(() => of(null))
    );
  };
}

// Cross-field validator
export const passwordMatchValidator: ValidatorFn = (form: AbstractControl) => {
  const password = form.get('password')?.value;
  const confirm = form.get('confirmPassword')?.value;
  return password === confirm ? null : { passwordMismatch: true };
};

Error Display Pattern

@Component({
  template: `
    <input formControlName="email" />
    @if (form.controls.email.hasError('required') && form.controls.email.touched) {
      <span class="error">Email is required</span>
    }
    @if (form.controls.email.hasError('email') && form.controls.email.touched) {
      <span class="error">Invalid email format</span>
    }
  `
})

Anti-Patterns

Anti-PatternWhy It's BadCorrect Approach
Template-driven for complex formsHard to test, no type safetyUse reactive forms
Not using nonNullableNullable types everywhereUse fb.nonNullable.group()
Validation in componentNot reusableExtract to validator functions
Subscribing to valueChanges without cleanupMemory leaksUse takeUntilDestroyed()

Quick Troubleshooting

IssueLikely CauseSolution
Form always invalidMissing required field valueCheck initial values
Type errors with controlsUsing untyped FormGroupUse fb.nonNullable.group()
Async validator fires too oftenNo debounceAdd updateOn: 'blur'
FormArray changes not reflectedMissing trackByUse track in @for
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/frontend-frameworks/angular-forms

Default branch

main

Latest commit

9496306

Tree SHA

fe4e2f1