Skip to main content
The FormGroup class tracks the value and validity state of a group of FormControl instances. It aggregates the values of each child control into one object, with each control name as the key. It calculates its status by reducing the status values of its children. FormGroup is one of the four fundamental building blocks used to define forms in Angular, along with FormControl, FormArray, and FormRecord.

Import

import { FormGroup, FormControl } from '@angular/forms';

Constructor

controls
TControl
required
A collection of child controls. The key for each child is the name under which it is registered.
validatorOrOpts
ValidatorFn | ValidatorFn[] | AbstractControlOptions | null
A synchronous validator function, an array of such functions, or an AbstractControlOptions object that contains validation functions and a validation trigger.
asyncValidator
AsyncValidatorFn | AsyncValidatorFn[] | null
A single async validator or array of async validator functions.

Basic Usage

Creating a FormGroup

import { FormGroup, FormControl, Validators } from '@angular/forms';

const form = new FormGroup({
  firstName: new FormControl('Nancy', Validators.minLength(2)),
  lastName: new FormControl('Drew'),
  email: new FormControl('', [Validators.required, Validators.email])
});

console.log(form.value);
// { firstName: 'Nancy', lastName: 'Drew', email: '' }

console.log(form.status); // 'INVALID' (email is required)

Type Safety

FormGroup accepts a generic type argument that describes its inner controls:
interface UserForm {
  firstName: FormControl<string>;
  lastName: FormControl<string>;
  age: FormControl<number>;
}

const form = new FormGroup<UserForm>({
  firstName: new FormControl('', { nonNullable: true }),
  lastName: new FormControl('', { nonNullable: true }),
  age: new FormControl(0, { nonNullable: true })
});

// form.value is typed as { firstName: string, lastName: string, age: number }

Optional Controls

const form = new FormGroup<{
  firstName: FormControl<string>;
  middleName?: FormControl<string>; // Optional
  lastName: FormControl<string>;
}>({
  firstName: new FormControl('', { nonNullable: true }),
  lastName: new FormControl('', { nonNullable: true })
});

// middleName can be added later
form.addControl('middleName', new FormControl('', { nonNullable: true }));

Properties

controls
TControl
A collection of child controls keyed by name.
value
object
The aggregate value of the FormGroup, including only enabled controls. Disabled controls are excluded.
status
'VALID' | 'INVALID' | 'PENDING' | 'DISABLED'
The validation status of the group. A FormGroup is invalid if any of its children are invalid.
valid
boolean
A group is valid when its status is VALID.
invalid
boolean
A group is invalid when any of its child controls are invalid.
errors
ValidationErrors | null
An object containing any errors generated by group-level validators.
pristine
boolean
A group is pristine if all of its children are pristine.
dirty
boolean
A group is dirty if any of its children are dirty.
touched
boolean
A group is touched if any of its children are touched.
valueChanges
Observable<any>
A multicasting observable that emits an event every time the value of the group changes.
statusChanges
Observable<FormControlStatus>
A multicasting observable that emits an event every time the validation status recalculates.

Methods

get()

Retrieves a child control given the control’s name or path.
path
string | (string | number)[]
required
The path to the control. Can be a string or an array for nested paths.
const form = new FormGroup({
  name: new FormControl(''),
  address: new FormGroup({
    street: new FormControl(''),
    city: new FormControl('')
  })
});

const nameControl = form.get('name');
const streetControl = form.get(['address', 'street']);
const cityControl = form.get('address.city');

setValue()

Sets the value of the FormGroup. It accepts an object that matches the structure of the group.
value
object
required
The new value for the group that matches the structure of the group.
options.onlySelf
boolean
default:"false"
When true, each change only affects this control, and not its parent.
options.emitEvent
boolean
default:"true"
When true, both the statusChanges and valueChanges observables emit events.
const form = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl('')
});

form.setValue({ firstName: 'Nancy', lastName: 'Drew' });
console.log(form.value); // { firstName: 'Nancy', lastName: 'Drew' }

// Throws error if structure doesn't match
// form.setValue({ firstName: 'Nancy' }); // Error!

patchValue()

Patches the value of the FormGroup. It accepts an object with control names as keys and does its best to match the values to the correct controls.
value
object
required
The object that matches the structure of the group.
options.onlySelf
boolean
default:"false"
When true, each change only affects this control, and not its parent.
options.emitEvent
boolean
default:"true"
When true, both the statusChanges and valueChanges observables emit events.
const form = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl('')
});

// Can patch partial values
form.patchValue({ firstName: 'Nancy' });
console.log(form.value); // { firstName: 'Nancy', lastName: '' }

reset()

Resets the FormGroup, marking all descendants pristine and untouched, and resetting the value of all descendants to their default values or null.
value
object
Resets the group with an initial value. Can include form state objects with value and disabled properties.
options.onlySelf
boolean
default:"false"
When true, each change only affects this control, and not its parent.
options.emitEvent
boolean
default:"true"
When true, both the statusChanges and valueChanges observables emit events.
const form = new FormGroup({
  firstName: new FormControl('Nancy'),
  lastName: new FormControl('Drew')
});

form.reset({ firstName: 'John', lastName: 'Doe' });

// Reset with disabled state
form.reset({
  firstName: { value: 'John', disabled: true },
  lastName: 'Doe'
});

getRawValue()

The aggregate value of the FormGroup, including any disabled controls. Retrieves all values regardless of disabled status.
const form = new FormGroup({
  firstName: new FormControl('Nancy'),
  lastName: new FormControl({ value: 'Drew', disabled: true })
});

console.log(form.value); // { firstName: 'Nancy' }
console.log(form.getRawValue()); // { firstName: 'Nancy', lastName: 'Drew' }

addControl()

Adds a control to this group. Updates the value and validity of the control.
name
string
required
The control name to add to the collection.
control
AbstractControl
required
Provides the control for the given name.
options.emitEvent
boolean
default:"true"
When true, both the statusChanges and valueChanges observables emit events.
const form = new FormGroup({
  firstName: new FormControl('')
});

form.addControl('lastName', new FormControl(''));
console.log(form.value); // { firstName: '', lastName: '' }

removeControl()

Removes a control from this group. Updates the value and validity of the control.
name
string
required
The control name to remove from the collection.
options.emitEvent
boolean
default:"true"
When true, both the statusChanges and valueChanges observables emit events.
form.removeControl('lastName');
console.log(form.value); // { firstName: '' }

setControl()

Replaces an existing control. If a control with the given name does not exist, it will be added.
name
string
required
The control name to replace in the collection.
control
AbstractControl
required
Provides the control for the given name.
options.emitEvent
boolean
default:"true"
When true, both the statusChanges and valueChanges observables emit events.
form.setControl('firstName', new FormControl('New Control'));

contains()

Checks whether there is an enabled control with the given name in the group.
controlName
string
required
The control name to check for existence in the collection.
const hasFirstName = form.contains('firstName'); // true
const hasMiddleName = form.contains('middleName'); // false

// Returns false for disabled controls
form.get('firstName')?.disable();
const hasFirstName = form.contains('firstName'); // false

registerControl()

Registers a control with the group’s list of controls. Does not update the value or validity of the control.
const control = new FormControl('');
form.registerControl('newField', control);

Group-Level Validators

You can add validators that consider the value of multiple child controls:
import { AbstractControl, ValidationErrors } from '@angular/forms';

function passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
  const password = group.get('password')?.value;
  const confirmPassword = group.get('confirmPassword')?.value;
  
  return password === confirmPassword ? null : { passwordMismatch: true };
}

const form = new FormGroup(
  {
    password: new FormControl('', Validators.minLength(8)),
    confirmPassword: new FormControl('', Validators.minLength(8))
  },
  { validators: passwordMatchValidator }
);

console.log(form.errors); // { passwordMismatch: true } if passwords don't match

Update Strategies

Set a default updateOn value for all child controls:
const form = new FormGroup(
  {
    firstName: new FormControl(''),
    lastName: new FormControl('')
  },
  { updateOn: 'blur' }
);

// All controls will update on blur unless overridden

Examples

Nested Form Groups

const form = new FormGroup({
  name: new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl('')
  }),
  address: new FormGroup({
    street: new FormControl(''),
    city: new FormControl(''),
    zipCode: new FormControl('')
  })
});

console.log(form.value);
// {
//   name: { firstName: '', lastName: '' },
//   address: { street: '', city: '', zipCode: '' }
// }

Dynamic Form Controls

const form = new FormGroup({
  email: new FormControl('')
});

// Add controls dynamically
function addPhoneNumber() {
  form.addControl('phone', new FormControl(''));
}

// Remove controls dynamically
function removePhoneNumber() {
  form.removeControl('phone');
}

Listening to Value Changes

const form = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl('')
});

form.valueChanges.subscribe(value => {
  console.log('Form value changed:', value);
});

form.patchValue({ firstName: 'Nancy' });
// Logs: Form value changed: { firstName: 'Nancy', lastName: '' }

Conditional Validation

const form = new FormGroup({
  country: new FormControl(''),
  state: new FormControl('')
});

form.get('country')?.valueChanges.subscribe(country => {
  const stateControl = form.get('state');
  
  if (country === 'USA') {
    stateControl?.setValidators(Validators.required);
  } else {
    stateControl?.clearValidators();
  }
  
  stateControl?.updateValueAndValidity();
});

FormRecord

FormRecord is a variant of FormGroup for dynamic keys:
import { FormRecord, FormControl } from '@angular/forms';

const numbers = new FormRecord({
  home: new FormControl('555-1234')
});

// Add controls with dynamic keys
numbers.addControl('work', new FormControl('555-5678'));
numbers.addControl('mobile', new FormControl('555-9012'));

console.log(numbers.value);
// { home: '555-1234', work: '555-5678', mobile: '555-9012' }

See Also

Build docs developers (and LLMs) love