valid, invalid, dirty, touched, pristine
Form Status & Error Display
Each control tracks its own status. Use those flags to show errors at the right time and to reset or patch values cleanly.
What you'll learn
- Read valid, invalid, dirty, touched, pristine
- Show errors after blur (touched)
- Reset and patch values
A FormControl is more than a value. It also tracks how the user has interacted with it. Knowing the difference matters for UX.
The Status Flags
| Flag | Meaning |
|---|---|
| valid | Passes every validator |
| invalid | Fails at least one validator |
| pending | Async validator is running |
| pristine | Value has not changed from initial |
| dirty | Value has changed at least once |
| untouched | User has not blurred this control yet |
| touched | User has blurred this control |
| disabled | Excluded from validation and the form value |
Show Errors at the Right Time
Showing “Required” on a field the user has not even visited is annoying. Gate error messages on touched.
@let emailCtrl = form.controls.email;
<input formControlName="email" />
@if (emailCtrl.invalid && (emailCtrl.touched || submitted())) {
<p class="err">Please enter a valid email.</p>
} When the form is submitted, you typically want to surface every error — markAllAsTouched() is the helper.
submit() {
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
this.api.save(this.form.value).subscribe();
} Updating Values
form.setValue({ ... })— replace the full value. Every field must be present, type-checked.form.patchValue({ ... })— replace some fields. Missing keys are ignored.form.reset()— back to the initial state, also clears touched/dirty flags.form.reset({ email: 'a@b.com' })— reset to a custom starting point.
Disabling Fields
control.disable() and control.enable() toggle a control. Disabled controls are not included in form.value — pass { emitEvent: false } if you do not want the change to fire valueChanges.