Skip to main content

Reactive forms

This article takes a look at Angular’s ReactiveFormsModule and the benefits it brings when working with forms, user input, form validation and more.

Limitations of ngModel

In Angular, there are several ways to reactively work with inputs and their values. A most simple approach would be to create a property containing the value, set the value of an input and listen for changes:

public username = '';
<input [value]="username" (input)="username = $event" />

or, using ngModel:

<input [(ngModel)]="username" />

This works fine if you have, for example, a simple text input to filter a list of items in an array. But when you need to add more functionalities, such as form validation, things can get out of hand quickly.

Template vs model driven

They are more powerful, easier to use and encourage a better separation between view and business logic. The Reactive approach removes validation logic from the template, keeping the templates cleaner. support better more advanced use cases via its Observable-based API.

Use reactive forms

Angular provides a comprehensive way for working with forms, using their ReactiveForms module. Make sure your module imports the ReactiveFormsModule in your (feature) module.

Read the docs

Please refer to the Angular guide about reactive forms for more in-depth information.

Since Angular 14, forms are now fully type-safe. The examples in this article will not define types explicitly, and will therefor also work in older versions, although the TS compiler will not be able to verify type safety. To get a quick overview of how types forms work, check out this video.

Building forms

When using reactive forms, you will be using the FormControl, FormArray and FormGroup extensively. For a short overview of these building blocks, refer to this section of the docs.

Let’s say you have a user object, with the following interface:

interface User {
name: string;
active: boolean;
interests: Array<string>;
address: {
street: string;
zipCode: string;
city: string;
};
}

Using constructors

The corresponding form group constructor would look something like this:

const form = new FormGroup({
firstName: new FormControl(''),
active: new FormControl(false),
interests: new FormControl([]),
address: new FormGroup({
street: new FormControl(''),
zipCode: new FormControl(''),
city: new FormControl(''),
}),
});

Using FormBuilder class

An even better approach is to use the FormBuilder:

// Inject the FormBuilder
const builder = inject(FormBuilder);

// Create new form
const form = builder.group({
firstName: '',
active: false,
interests: [],
address: builder.group({
street: '',
zipCode: '',
city: '',
}),
});

Form validation

Angular comes with several built-in form validation functions, available in the Validator class.

Read the docs

Please refer to the Angular guide about form validation for more in-depth information.

Custom validation

We recommend writing your own form validators in a class. You can extend the Validators class, or write your own class.

Async form validation

Create custom inputs

This section will show how you can easily create custom components that implement support for ReactiveForms. This will come in handy when you want to create high-order inputs that support working with [formControl]. There are several reasons to create a custom component:

For lists of entities in your state, that you want to be able to select with a dropdown (one or many).

For inputs that have unique interfaces.

Caveats

Avoid using formControl as @Input() name

When using model driven forms, you connect your FormControl to an input using the [formControl] directive. This add some built-in functionality. Creating a custom component that implements an @Input with the same name, some things will conflict. You will get errors that say something about ngDefaultControl.

Difference between value and getRawValue()

To get the current value of a form, you can access the value property, or call getValue() method.

Disabled controls

When using model driven forms, you have to disable controls by calling the .disable() method on the FormControl. Note that these controls will not show up in your form’s value. The key will not be present in the value object.