home / skills / gentleman-programming / gentleman-skills / forms

forms skill

/curated/angular/forms

This skill helps you choose and use Angular forms best practices, including signals and reactive forms, to validate, manage state, and simplify form handling.

npx playbooks add skill gentleman-programming/gentleman-skills --skill forms

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
2.7 KB
---
name: angular-forms
description: >
  Angular forms: Signal Forms (experimental) and Reactive Forms.
  Trigger: When working with forms, validation, or form state in Angular.
metadata:
  author: gentleman-programming
  version: "1.0"
---

## When to Use What

| Use Case | Recommendation |
|----------|----------------|
| New apps with signals | Signal Forms (experimental) |
| Production apps | Reactive Forms |
| Simple forms | Template-driven |

---

## Signal Forms (v21+, experimental)

```typescript
import { form, FormField, required, email } from '@angular/forms/signals';

@Component({
  imports: [FormField],
  template: `
    <form>
      <input [formField]="emailField" type="email" />
      <input [formField]="passwordField" type="password" />
      <button (click)="submit()">Login</button>
    </form>
  `
})
export class LoginComponent {
  readonly loginForm = form({
    email: ['', [required, email]],
    password: ['', required]
  });
  
  readonly emailField = this.loginForm.controls.email;
  readonly passwordField = this.loginForm.controls.password;
  
  submit() {
    if (this.loginForm.valid()) {
      const values = this.loginForm.value();
    }
  }
}
```

### Signal Forms Benefits
- Automatic two-way binding
- Type-safe field access
- Schema-based validation
- Built on signals

---

## Reactive Forms (production)

```typescript
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';

@Component({
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="submit()">
      <input formControlName="email" type="email" />
      <input formControlName="password" type="password" />
      <button type="submit" [disabled]="form.invalid">Login</button>
    </form>
  `
})
export class LoginComponent {
  private readonly fb = inject(FormBuilder);

  form = this.fb.nonNullable.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]],
  });
  
  submit() {
    if (this.form.valid) {
      const { email, password } = this.form.getRawValue();
    }
  }
}
```

### Key Points
- ALWAYS use `fb.nonNullable.group()` for type safety
- Use `getRawValue()` to get typed values
- Reactive Forms are synchronous (easier to test)

---

## Nested Forms & FormArray

```typescript
form = this.fb.nonNullable.group({
  name: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
  }),
  phones: this.fb.array([this.fb.control('')]),
});

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

addPhone() {
  this.phones.push(this.fb.control(''));
}
```

---

## Resources

- https://angular.dev/guide/forms/signals/overview
- https://angular.dev/guide/forms/reactive-forms

Overview

This skill helps you choose and implement Angular form patterns: Signal Forms (experimental) and Reactive Forms, plus guidance for nested structures and FormArray. It summarizes when to use each approach, key APIs, and practical tips for validation, type safety, and state handling. Use it when building or refactoring forms in Angular applications.

How this skill works

The skill inspects form requirements (signals compatibility, production readiness, complexity) and recommends Signal Forms for new signal-based apps or Reactive Forms for production stability. It explains core APIs: signal form() with FormField bindings and automatic two-way updates, and FormBuilder nonNullable.group() with formControlName, validators, and synchronous state for testing. It also covers nested FormGroup and FormArray patterns and how to read typed values.

When to use it

  • Start new projects that use Angular signals: Signal Forms (experimental) for concise, type-safe bindings.
  • Use Reactive Forms for production apps that need maturity, testability, and synchronous behaviour.
  • Choose template-driven forms only for very simple, small forms with minimal logic.
  • Use Signal Forms when you want automatic two-way binding and signal-based reactivity.
  • Use Reactive Forms when you require guaranteed stability, complex validation, or wide library support.

Best practices

  • For Reactive Forms always use fb.nonNullable.group() to preserve non-null types and avoid type casts.
  • Use getRawValue() on Reactive Forms to retrieve a typed snapshot of form values safely.
  • Keep validation declarative: use Validators with Reactive Forms or schema validators with Signal Forms for consistent error handling.
  • Encapsulate nested groups and arrays in small helper methods or components to keep components readable.
  • Prefer synchronous operations in Reactive Forms for easier unit testing; use async validators only when necessary.

Example use cases

  • Login form with email and password: Reactive Forms for production, Signal Forms for a signals-based app prototype.
  • Complex profile editor with nested address and dynamic phone numbers: Reactive Forms with FormArray and nested FormGroup.
  • New app using signals: build forms with form(), export FormField bindings directly into inputs for automatic two-way updates.
  • Migration plan: keep Reactive Forms in production modules and experiment with Signal Forms in feature branches before wider adoption.

FAQ

Are Signal Forms production ready?

Signal Forms are experimental as of Angular v21+; they are great for new signal-first apps but prefer Reactive Forms for production stability.

How do I get typed values from a Reactive Form?

Use fb.nonNullable.group() to maintain types and call getRawValue() to retrieve a typed object of current values.

How do I handle dynamic lists of controls?

Use FormArray in Reactive Forms: create controls with fb.control(), expose a getter for the array, and push/pop controls. Encapsulate add/remove logic in methods.