skip to content

Forms

The @angular/forms package offers three models: template-driven ngModel, reactive FormGroup trees and signal-based form(). Interviewers ask which to pick and how each one tracks state.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

In Angular reactive forms, how do FormControl, FormGroup and FormArray fit together, and how does a template bind to them?

level: juniorimportance: must knowfreq 80%

answer

  1. three kinds of AbstractControl
  2. one value, named children, indexed children
  3. the model lives in the class
  4. formGroup, formControlName, formGroupName
  5. ReactiveFormsModule in imports

basics

~20 s

FormControl holds one value, FormGroup holds named child controls, and FormArray holds an indexed list; all extend AbstractControl and nest into a tree built in the class. The template binds it with [formGroup], formControlName, formGroupName and formArrayName from ReactiveFormsModule.

solid answer

~40 s

All three classes extend `AbstractControl`, so each has a `value`, a `status`, validators, `touched`/`dirty` flags and change streams. `FormControl` wraps a single field, `FormGroup` aggregates named children into an object value, and `FormArray` aggregates an ordered list into an array value. You build the tree in the component class, for example a shipping form with a `recipient` control and a nested `address` group, and the tree is the source of truth. In the template, after importing `ReactiveFormsModule`, `[formGroup]="form"` binds the root, `formGroupName="address"` steps into a nested group, `formControlName="city"` binds an input to a child by name, and `formArrayName` steps into an array. A lone control can bind directly with `[formControl]`. Values, validity and state bubble up from children to parents.

code

ts · 32 lines
ts
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';

@Component({
  selector: 'app-shipping-address',
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="save()">
      <input formControlName="recipient" />
      <div formGroupName="address">
        <input formControlName="street" />
        <input formControlName="city" />
        <input formControlName="postcode" />
      </div>
      <button type="submit" [disabled]="form.invalid">Ship here</button>
    </form>
  `,
})
export class ShippingAddress {
  readonly form = new FormGroup({
    recipient: new FormControl('', {nonNullable: true, validators: Validators.required}),
    address: new FormGroup({
      street: new FormControl('', {nonNullable: true}),
      city: new FormControl('', {nonNullable: true, validators: Validators.required}),
      postcode: new FormControl('', {nonNullable: true}),
    }),
  });

  save(): void {
    console.log(this.form.value); // {recipient, address: {street, city, postcode}}
  }
}

go deeper

for a junior

Know the three classes, that the tree is built in the class, and how [formGroup], formGroupName and formControlName bind the template to it.

for a middle

Explain the shared AbstractControl API, how values and status bubble from children to parents, and when [formControl] replaces formControlName.

for a senior

Structure large forms deliberately: nested groups that match the domain object, sub-groups passed to child components, and paths that survive refactors.

for a principal

Decide where form models live and how they map to API contracts, so that several teams build forms the same way.

## One base class, three shapes Angular's reactive forms model every piece of a form as an **`AbstractControl`**. Three concrete classes give it shape: | Class | Holds | `value` shape | Typical use | |---|---|---|---| | `FormControl` | one value | the value itself | a text input, a checkbox, a select | | `FormGroup` | named children | an object keyed by child name | a whole form, or a section such as an address | | `FormArray` | ordered children | an array | a list of phone numbers or order lines | Because they share the base class, every node offers the same toolkit: - **State**: `value`, `status` (`VALID`, `INVALID`, `PENDING`, `DISABLED`), `errors`, `touched`/`dirty`. - **Writes**: `setValue()`, `patchValue()`, `reset()`, `enable()`/`disable()`. - **Navigation**: `get('address.city')` finds a descendant by path. - **Streams**: `valueChanges`, `statusChanges` and the unified `events` observable. Changes **bubble up**: when a control's value changes, its parent group recomputes its own value and status, and so on to the root. ## Building the tree in the class The tree is created in TypeScript, which is the defining trait of reactive forms: the model exists before the template renders, so code can read, write and observe it immediately. A shipping form might be a root `FormGroup` with a `recipient` control and a nested `address` group holding `street`, `city` and `postcode`. `FormGroup` values are objects mirroring that nesting, so `form.value` looks like `{recipient: '...', address: {street: '...', city: '...', postcode: '...'}}`. ## Binding the template Import **`ReactiveFormsModule`** in the standalone component, then connect elements to the tree with directives: 1. **`[formGroup]="form"`** on the `<form>` binds the root group and provides it to descendants. 2. **`formGroupName="address"`** on a wrapper steps into a nested group by name. 3. **`formControlName="city"`** on an input binds it to the child control with that name in the nearest group. 4. **`formArrayName="phones"`** steps into a `FormArray`; inside it, children are addressed by index. 5. **`[formControl]="searchBox"`** binds a standalone `FormControl` that is not part of any group, such as a search field. The directives use `ControlValueAccessor` implementations to move values between the DOM and the controls. View-to-model and model-to-view updates are **synchronous**, unlike template-driven forms. ## Common binding mistakes - **Forgetting `ReactiveFormsModule`**: the compiler rejects `[formGroup]` as an unknown property. - **A `formControlName` outside any `[formGroup]`**: Angular throws, because the directive needs a parent container to look the name up in. - **A name that is not in the group**: Angular throws "Cannot find control with name", which usually means a typo or a nesting mismatch. - **Using `ngModel` inside `[formGroup]`**: Angular throws and points you to `formControlName`, because the two styles cannot share one form. ## Splitting a large form across components Because the model is an object, a parent can hand a **sub-group** to a child component as an input, and the child binds it with its own `[formGroup]`: - the parent keeps one tree, so `form.value` and `form.valid` still cover everything; - the child needs `ReactiveFormsModule` and an input typed as the sub-group, for example the address group; - no dependency-injection bridging is needed, unlike template-driven forms, because nothing is looked up across the component boundary. This is the usual way to reuse an address block in both the billing and the shipping section of a checkout. ## Why teams choose this model - The model is plain TypeScript, so it can be **unit-tested without a template**. - Values are **typed**: `new FormControl('')` is inferred from its initial value. - Code can **react** to changes, **add or remove** controls at runtime and **compose** validators. - Large forms can be split across components by passing sub-groups down as inputs. For small, fixed forms that extra setup can be overkill, which is where template-driven forms still fit.

  • When would you bind with [formControl] instead of formControlName?
    `[formControl]` takes a `FormControl` instance directly, so it suits a control that belongs to no group, such as a search box or a filter field. `formControlName` takes a string and looks the control up in the nearest parent group, which is the normal choice inside a `[formGroup]` form.
  • How do you read the city control from the class without walking the tree by hand?
    Use `form.get('address.city')` with a dot-separated path, or the typed property path `form.controls.address.controls.city`. The second form keeps full type information, while `get()` returns a possibly-null `AbstractControl` that you narrow before use.

saying these in an interview costs you the question

  • FormGroup and FormArray are unrelated to FormControl
  • Reactive forms build their controls from the template markup
  • formControlName works without a parent formGroup
  • Reactive forms update the model asynchronously like ngModel
  • FormsModule is enough to use formGroup in a standalone component
open as a page

In Angular Signal Forms, how do form(), a schema function and the [formField] directive work together in a registration form?

level: juniorimportance: must knowfreq 48%

basics

~20 s

The data lives in a writable signal; form(model, schema) returns a FieldTree mirroring it; the schema runs once, binding rules like required() to paths; [formField] two-way binds an input to a field, whose state you read by calling it.

open as a page

In Angular, how do template-driven forms differ from reactive forms, and when is a template-driven form the right choice?

level: juniorimportance: must knowfreq 75%

basics

~20 s

Template-driven forms let ngModel and NgForm directives build the FormControl tree implicitly from the markup, bound to plain component properties; reactive forms build that tree explicitly in the class. Template-driven fits small, static forms such as a newsletter sign-up.

open as a page

In Angular's typed reactive forms, why is new FormControl('') inferred as FormControl<string | null>, and how do you remove the null?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Calling reset() on a plain FormControl sets its value to null, so Angular adds null to the inferred type. Passing {nonNullable: true} makes reset() return to the initial value instead, and the type becomes FormControl<string>.

open as a page

In Angular reactive forms, how do you attach built-in validators such as Validators.required and Validators.minLength, and how do you read the errors they produce?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Angular's built-in validators are functions passed to a control, either as the second argument or in the validators option. Each failing validator adds a key to control.errors, which is null when the control is valid.

open as a page

In Angular, what does a ControlValueAccessor do, and what is each of its four methods for in a custom star-rating control?

level: middleimportance: must knowfreq 62%

basics

~20 s

A ControlValueAccessor bridges a FormControl and a UI element: writeValue pushes the model value into the view, registerOnChange and registerOnTouched hand over callbacks the component calls on user edits and blur, and optional setDisabledState mirrors the disabled status.

open as a page

In an Angular reactive order form, how do you let users add and remove line items and bind each row in the template?

level: middleimportance: must knowfreq 68%

basics

~20 s

Model the line items as a FormArray of FormGroups, add rows with push() and remove them with removeAt(index). In the template, wrap the rows in formArrayName, loop over its controls with @for, and bind each row with [formGroupName]="$index".

open as a page

In Angular reactive forms, what is the difference between setValue() and patchValue() on a FormGroup, and when does each one throw?

level: middleimportance: must knowfreq 78%

basics

~10 s

setValue() requires a value for every child control and rejects unknown keys, throwing otherwise; patchValue() updates only the keys it receives and ignores the rest. On a single FormControl both behave the same.

open as a page

In Angular reactive forms, how do you implement a validator that checks the password and confirm-password fields match, and where does its error appear?

level: middleimportance: must knowfreq 66%

basics

~20 s

Write a ValidatorFn that reads both child values and returns an error such as {passwordMismatch: true}, and attach it to the FormGroup through the validators option. The error lands on the group's errors, not on either control.

open as a page

In Angular, why does formControlName on a custom <app-star-rating> component throw 'No value accessor for form control', and how do you fix it?

level: juniorimportance: should knowfreq 50%

basics

~20 s

formControlName looks for an NG_VALUE_ACCESSOR on its own element. A plain component provides none and the built-in accessors target native inputs, so Angular throws NG01203. Implement ControlValueAccessor and provide NG_VALUE_ACCESSOR with useExisting, forwardRef and multi: true.

open as a page

In Angular reactive forms, which FormArray methods add, insert, replace and remove controls, and what does each one do to the array?

level: juniorimportance: should knowfreq 52%

basics

~10 s

A FormArray changes shape through push(), insert(index, c), removeAt(index), setControl(index, c) and clear(). Each one updates the array's value and validity and emits valueChanges unless emitEvent is false, but none marks the array dirty.

open as a page

In an Angular standalone component, why does [(ngModel)] fail with NG8002, and why does an ngModel inside a <form> also need a name?

level: juniorimportance: should knowfreq 62%

basics

~20 s

NgModel is declared in FormsModule, so a standalone component that does not import FormsModule has no ngModel input and the compiler reports NG8002. Inside a form, NgForm registers each ngModel under its name, so a missing name throws unless the control is marked standalone.

open as a page

How does an Angular custom star-rating control validate itself through NG_VALIDATORS, and how do its errors combine with the FormControl's own validators?

level: middleimportance: should knowfreq 34%

basics

~20 s

The component implements Validator's validate(control), returning an errors object or null, and provides NG_VALIDATORS with useExisting, forwardRef and multi: true. The forms directive on that element merges it with the FormControl's own validators, so both run and their errors combine.

open as a page

In Angular, why does patchValue on an order form with an empty items FormArray drop a saved order's line items, and how do you load them?

level: middleimportance: should knowfreq 46%

basics

~20 s

patchValue and setValue only write values into controls that already exist, and a FormArray never creates rows from data. Rebuild the array first, with clear() and push() per line or setControl('items', newArray), then patch or set the values.

open as a page

In Angular reactive forms, why does form.value leave out a disabled control, and how should you disable a control bound with formControlName?

level: middleimportance: should knowfreq 55%

basics

~20 s

A disabled control is excluded from its parent's value and validity, so form.value omits it while getRawValue() includes it. Disable it through the model with control.disable() or {value, disabled: true}; a [disabled] binding on formControlName only logs a warning.

open as a page

In Angular reactive forms, what does FormBuilder add over calling new FormGroup() directly, and when would you inject NonNullableFormBuilder instead?

level: middleimportance: should knowfreq 50%

basics

~20 s

FormBuilder is an automatically provided service that turns plain values and arrays into FormControl, FormGroup, FormRecord and FormArray instances; it adds no new behaviour. NonNullableFormBuilder builds every implicit control with nonNullable: true, so reset() restores initial values instead of null.

open as a page

In Angular Signal Forms, how do you add a password-confirmation rule and an async username-availability check to a registration schema?

level: middleimportance: should knowfreq 36%

basics

~20 s

validate() on confirmPassword compares value() with valueOf(path.password) and re-runs when either changes. validateHttp() or validateAsync() on username runs only after the sync rules pass, sets pending() meanwhile, and maps the answer to an error or null.

open as a page

In Angular Signal Forms, how do you build a custom phone-number input for a registration form by implementing FormValueControl?

level: middleimportance: should knowfreq 32%

basics

~20 s

Implement FormValueControl<string> with value = model(''). [formField] syncs it with the field and fills optional inputs such as disabled, errors and touched; the component emits a touch output on blur. No providers or register callbacks are needed.

open as a page

In Angular Signal Forms, what does submit() do for a registration form, and how does a server's 'username taken' error reach the username field?

level: middleimportance: should knowfreq 30%

basics

~20 s

submit() marks fields touched, calls onInvalid instead if any rule failed, otherwise runs the action with submitting() true. Errors the action returns, optionally targeting a fieldTree, land in that field's errors() and clear when the user edits it.

open as a page

In an Angular template-driven form, what do #f="ngForm" and ngModelGroup give you, and what shape does f.value take?

level: middleimportance: should knowfreq 48%

basics

~10 s

#f="ngForm" exposes the NgForm directive: its FormGroup, value, validity, submitted flag, ngSubmit event and resetForm(). ngModelGroup="address" adds a nested FormGroup, so f.value becomes an object keyed by names, with {address: {...}} for grouped fields.

open as a page

In a typed Angular FormGroup for user settings, why is form.value typed as Partial, and when should you call getRawValue() instead?

level: middleimportance: should knowfreq 52%

basics

~10 s

An Angular FormGroup leaves disabled children out of its value, so every key is typed possibly undefined. getRawValue() includes disabled controls and returns the full, non-Partial type, which suits saving a whole settings record.

open as a page

In Angular reactive forms, how do you write an AsyncValidatorFn that checks whether a username is taken, and what does the PENDING status mean?

level: middleimportance: should knowfreq 55%

basics

~10 s

An AsyncValidatorFn takes the control and returns an Observable or Promise of ValidationErrors or null. Angular runs it only when the sync validators pass, and the control's status is PENDING until the result arrives.

open as a page

An Angular color-picker ControlValueAccessor never shows its required error after blur, ignores form.disable(), and shows stale colours after patchValue; what is broken?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Each symptom is one accessor duty: onTouched is never called when focus leaves the picker, setDisabledState is missing or unrendered, and writeValue sets a plain field the default OnPush view never re-reads. Fix all three, holding displayed state in signals.

open as a page

How would you build an Angular reactive form from server-supplied field metadata, including fields that appear only when another field has a certain value?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Map each metadata entry to a FormControl with validators derived from it, add them to a group, and render with @for plus @switch on the field type using [formControlName]. Handle conditional fields with addControl/removeControl, or disable() to keep their values.

open as a page

An Angular reactive form copies billing into shipping on billing valueChanges, a shipping listener writes back, and the console fills with 'Maximum call stack size exceeded'. Why, and how do emitEvent: false and the events stream help?

level: seniorimportance: should knowfreq 45%

basics

~20 s

setValue() and patchValue() emit valueChanges synchronously, so two listeners that write into each other's controls recurse until the call stack overflows. Pass {emitEvent: false} for programmatic writes, or use the events stream's source to ignore changes you caused.

open as a page

For a new Angular 22 registration flow, would you choose Signal Forms or reactive forms, and which trade-offs decide it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

For a new signal-based Angular 22 app, Signal Forms: public API since v22, one signal model, inferred types, rules in one schema. Reactive forms stay the better fit for existing reactive codebases and valueChanges-heavy logic.

open as a page

In an Angular template-driven form, why are the form's controls still empty in ngAfterViewInit, so that form.setValue() throws, although the inputs are already rendered?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Template-driven forms register each ngModel with NgForm, and apply model values, in a microtask after the current change-detection pass. During ngAfterViewInit the group is still empty, so setValue() throws; bind values through ngModel or wait for the form to settle.

open as a page

In Angular typed forms, how do you choose between an optional FormGroup key, FormRecord and UntypedFormGroup when a form's keys are not fixed?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Use an optional key in a typed FormGroup interface when the key is known but sometimes absent, FormRecord when keys are open-ended but every control has the same type, and UntypedFormGroup only when keys are open-ended and types differ.

open as a page

An Angular sign-up form's username-availability validator sends a request on every keystroke and sometimes stays PENDING forever; how do you diagnose and fix it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Async validators run on every value change by default. Set updateOn: 'blur' on the control, or debounce inside the validator with timer() because Angular cancels the previous check, and make sure every path emits a result and completes.

open as a page

In an Angular template-driven form, how do attributes like required, minlength, pattern and email turn into validation, and why don't native browser bubbles appear?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

FormsModule ships validator directives whose selectors match attributes such as required, minlength, pattern and email on an element that also has ngModel; each adds a validator to that control. FormsModule also adds novalidate to forms, so Angular, not the browser, reports errors.

open as a page

showing 1–30 of 32