Angular Forms
Forms are where applications meet user input, and where validation, error messages, async checks, and accessibility all converge. Angular ships two mature form systems. Template-driven forms are defined mostly in HTML with ngModel. Reactive forms are defined in TypeScript as an explicit, strongly typed model of FormGroup, FormControl, and FormArray objects. A newer signal-based forms API builds forms on top of signals.
Reactive forms are the standard for anything non-trivial: they're testable without a DOM, fully typed since Angular 14, and make dynamic fields, cross-field rules, and async validation straightforward. This page focuses on them, with notes on the alternatives.
TL;DR
- Reactive forms: build the form model in TypeScript (
FormGroup,FormControl,FormArray) and bind with[formGroup]andformControlName. - Forms are typed; use
NonNullableFormBuilder(fb.nonNullable) so reset values aren'tnull. - Validate with built-in validators (
required,email,minLength,pattern…), custom validators, async validators (for example username availability), and group-level validators for cross-field rules. FormArrayhandles dynamic lists (line items, addresses).- Show errors when controls are touched or submitted, with accessible markup (
aria-invalid,aria-describedby). - Client validation is for UX; validate again on the server.
Quick Example
A typed reactive checkout form with a dynamic item list and a cross-field rule:
Core Concepts
Template-Driven vs Reactive Forms
Form Model Building Blocks
FormControl<T>: a single value with validation state (value,valid,errors,touched,dirty,pending).FormGroup<{...}>: a fixed set of named controls.FormArray<T>: an ordered, dynamic list of controls or groups.FormRecord: a dynamic set of keyed controls with the same type.FormBuilder/NonNullableFormBuilder: concise construction. Non-nullable controls reset to their initial value instead ofnull, which keeps types clean.
value excludes disabled controls; getRawValue() includes them. Observe changes through valueChanges and statusChanges (observables), or convert them with toSignal. Recent versions also expose unified control events.
Validation
- Built-in:
Validators.required,requiredTrue,email,min,max,minLength,maxLength,pattern. - Custom validators: functions returning
ValidationErrors | null. - Group validators: cross-field rules (password confirmation, date ranges) set on the
FormGroup. - Async validators: return an Observable or Promise, for server checks like "username taken". Set
updateOn: 'blur'or debounce to avoid a request per keystroke, and handle thependingstate. updateOn: 'change' | 'blur' | 'submit'controls when values and validation update.
Displaying Errors Accessibly
Show errors after a control is touched or the form is submitted, not while the user is still typing their first character. Link messages to inputs (aria-describedby), mark invalid fields (aria-invalid="true"), use real <label>s, announce errors (role="alert" or a live region), and move focus to the first invalid field on submit. See accessibility and forms validation.
Custom Form Controls
Components implementing ControlValueAccessor (writeValue, registerOnChange, registerOnTouched, setDisabledState) work with formControlName and ngModel, just like native inputs. Use this for date pickers, rich selects, and tag inputs in design systems.
Signal Forms
Angular's newer signal-based forms API (introduced as experimental) models form state with signals, with schema-based validation and fine-grained reactivity, integrating naturally with zoneless apps. Evaluate its stability status for your Angular version before adopting it widely. Reactive forms remain the stable default.
Best Practices
Use Typed, Non-Nullable Forms
Typed forms catch mismatches between the form model and your API types at compile time. Non-nullable builders avoid string | null everywhere after reset().
Keep Validation Rules Close to the Domain
Share validators (and ideally schemas) between forms and services. For complex apps, derive client-side rules from the same schema as the backend (for example JSON Schema or Zod on a Node backend) to avoid drift.
Always Re-Validate on the Server
Client-side validation improves UX, but anyone can bypass it. The server must enforce every rule and return field-level errors that the form can display.
Break Large Forms Into Components
Split long forms into sub-form components, each owning a FormGroup passed via input or using ControlContainer, or build multi-step wizards with a group per step.
Common Mistakes
Showing Errors Immediately on Load
Displaying "Email is required" before the user has touched anything feels hostile. Gate messages on touched, dirty, or submit.
Tracking FormArray Items by Index
Using $index for track in dynamic arrays can mix up DOM state when items are removed or reordered. Track the control object, or a stable ID stored in each group.
Forgetting Disabled Controls in value
form.value omits disabled controls, so submitted payloads silently miss fields. Use getRawValue() when you need everything.
FAQ
Should I use reactive or template-driven forms?
Reactive forms for most applications: they're explicit, strongly typed, easier to test, and handle dynamic structures and complex validation cleanly. Template-driven forms are fine for small, simple forms where the extra structure isn't worth it.
How do I validate that two fields match?
Add a validator to the parent FormGroup that compares the controls' values and returns an error object (for example { passwordMismatch: true }) on mismatch, and display it at the group level or on the confirmation field.
How do I add fields dynamically?
Use a FormArray of FormGroups. push() or insert() new groups and removeAt() to delete them. Iterate over formArray.controls in the template with formArrayName and [formGroupName]="i".
How do I integrate a custom input component with forms?
Implement ControlValueAccessor in the component and register it with the NG_VALUE_ACCESSOR provider. The component then works with formControlName, formControl, and ngModel, including validation and disabled state.
Related Topics
- Angular — The framework overview
- Angular Signals — Reactive state and signal forms
- Angular Components — Composing form components
- Forms & Validation — Form UX principles across frameworks
- Accessibility — Accessible error handling
- Zod — Schema validation shared with the backend