← Angular: components, reactivity and application decisions
05 / 10 · 65 MIN

Forms, validation and user intent

Build typed reactive form models and interpret validity, reset and submission. Separate collected data, error presentation and operation acceptance, including disabled fields and cross-field rules.

Explicit model and template directives

This lesson uses typed reactive forms from @angular/forms. The FormControl or FormGroup model is declared in TypeScript and bound to the template through ReactiveFormsModule directives. In a standalone component, importing only the FormControl class in the file does not expose [formControl] to the template: the appropriate dependency must be declared in imports.

In the example, [formGroup] associates the group and formControlName identifies each named control. Choosing this model does not mean every application must abandon other form models; it makes the studied contracts explicit.

Types, null and reset

A FormControl initialized with a string normally infers string|null because reset() without an argument can produce null. nonNullable changes both typing and behavior: reset restores the initial value. It is not equivalent to required, a non-empty string or validation of remote data.

A control initialized with null may need an explicit type such as string|null to accept strings later. In the example, title has an empty default and separate validators; reset restores that empty value and the field again fails presence and length rules.

Full updates, partial updates and data shape

setValue on a group requires a value matching its control structure. patchValue can update only the supplied part and can silently tolerate certain structural errors. A partial update suits changing title while preserving recordId; to verify that a response has the expected model, do not treat patchValue’s permissiveness as proof.

Validate the external contract before applying it. Method choice should follow intent: replace complete state or change known fields while preserving others.

Local rules and cross-field relationships

Synchronous validators receive a control and return null when no error exists or a ValidationErrors object when one does. Returning {valid: true} is not a success confirmation under that contract. When a rule compares sibling controls such as interval start and end, place validation on their common ancestor FormGroup.

Presentation policy is separate: showing a message after touched or dirty avoids reporting errors before appropriate interaction. Dynamically changing validators requires updateValueAndValidity to recalculate state; changing only the message does not update validity.

Asynchronous validation and pending submission

Asynchronous validators return a Promise or Observable with the result; the Observable must complete. Angular starts this phase only after synchronous validators succeed. While waiting, the control can be pending, a state distinct from valid and invalid.

If policy requires completed validation, blocking only invalid can be insufficient: require valid at admission. Also define communication-failure handling: confirmed availability should not be presented merely because the service could not be queried. Even a completed local check can become stale before final saving.

Disabled and payload selection

In an enabled group, disabled controls are omitted from value although they remain present in controls. The aggregate type can therefore be Partial and a field may be undefined in that read. getRawValue includes disabled control values. In the example, key R7 is required for collection, so the chosen method matches the contract.

The key does not become trusted because its field is disabled: clients can be modified and the server remains responsible for authorization and validity. Explicitly decide whether a field should be omitted, included or calculated by the recipient.

Collections, intent and summary

FormGroup represents known names; FormArray represents controls in an indexed collection; FormRecord allows open-ended keys with homogeneous controls. Do not replace business names with positions without considering identity. ngSubmit communicates submission intent and invokes its handler; it does not prove persistence.

The example produces a local collection message and makes no HTTP call. A complete flow should distinguish validation, in-flight request, rejection and acceptance. Selecting an option about these states is part of assessment without executing the example.

// Reading example: local draft only; no API persistence or application bootstrap.
import {Component, signal} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';

@Component({
  selector: 'record-draft', standalone: true, imports: [ReactiveFormsModule],
  template: `<form [formGroup]="form" (ngSubmit)="collect()">
    <label>Record <input formControlName="recordId"></label>
    <label>Title <input formControlName="title"></label>
    @if (form.controls.title.invalid && form.controls.title.touched) {
      <p>A title of at least three characters is required.</p>
    }
    <button type="submit" [disabled]="!form.valid">Collect draft</button>
    <button type="button" (click)="form.reset()">Reset</button>
  </form><p>{{ notice() }}</p>`,
})
export class RecordDraft {
  readonly form = new FormGroup({
    recordId: new FormControl({value: 'R7', disabled: true}, {nonNullable: true}),
    title: new FormControl('', {
      nonNullable: true, validators: [Validators.required, Validators.minLength(3)],
    }),
  });
  readonly notice = signal('No draft collected');
  collect() {
    this.form.markAllAsTouched();
    if (!this.form.valid) return;
    const draft = this.form.getRawValue();
    this.notice.set('Collected ' + draft.recordId + ': ' + draft.title);
  }
}
IN PRACTICE

The group remains enabled because title is enabled; recordId remains in the model while disabled. Collection uses getRawValue to include both fields. The handler requires valid and the message confirms only local collection.

Reset restores initial nonNullable control values; it must not be interpreted as deleting data already saved elsewhere.

Common pitfalls

Confusing nonNullable with required; expecting disabled to remove the control; using patchValue as schema verification; accepting pending as valid; returning a success object from a validator; interpreting ngSubmit as save confirmation.

Related topics: Signals, inputs and output contracts · HTTP, streams and response acceptance · Testing behavior and integration limits

Take this idea with you

A form expresses a local model and intent. Choose structure, reset, validation and collection explicitly, and communicate remote success only with corresponding evidence. Omitted fields and pending are states to interpret rather than details to ignore.

Create account

Reference: Reactive model and template · Angular 22; standalone components