The updateOn Option in Angular Forms
When using Angular Forms, by default, on every keystroke, the values of your form controls are updated and their associated validators are executed. This may not be always desirable. The updateOn option gives us a finer-grained control over the moment when value updates and validators are triggered.

The validation of user inputs plays a crucial role when dealing with forms. It prevents the user from submitting incomplete or invalid data. This ensures they don't get frustrated by having to submit the same invalid form over and over. In Angular forms, validation takes the shape of functions that get passed in the user inputs, and tells us whether the user-entered data is correct or not.
By default, on every keystroke, the values of our form controls are updated. These validator functions then get executed. This may not be always desirable. Sometimes we want a finer-grained control over the moment when value updates and validators are triggered. This is where Angular forms' updateOn
option comes into play.
In this article, we will explain what the updateOn
option is, why it's important, and uncover how to use it to avoid degrading our Angular applications' performance.
If we want to understand the problem and the value proposition of the updateOn
option, we first need to understand how Angular forms work. Let's say we have a bare-bones HTML form in our UI with no Angular involved:
<form>
<label>
Full Name
<input type="text" name="fullName"/>
</label>
<label>
Email
<input type="email" name="email"/>
</label>
<button type="submit">Submit</button>
</form>
This form contains two inputs and a submit button. The first input—fullName
—represents the user's full name, and the second input—email
—is the user's email address. How do we handle this form in Angular?
One goal, two techniques
To handle this form in Angular, we have two different techniques at our disposal: template-driven and reactive. In the template-driven approach, the responsibility of the form is put inside the HTML template, whereas in the reactive approach, it's the component class who is in charge of the form. So, these two techniques are fundamentally different in the way the developer wires up the form. But under the hood they use the same foundations, and achieve the same goal which is to track:
- the value the user has entered in the UI: the raison d'être of the form, what we wanted to capture by displaying the form to the user in the first place;
- what interactions the user has with the form inputs;
- whether errors exist on the form inputs or not.
To achieve this goal, Angular provides building blocks that define a form model. What's a form model, and what are these building blocks? Let's find out!
The form model
The form model is the data structure used by Angular to represent an HTML form. This is what does the glue between the form elements in the HTML and Angular. To create this form model, Angular gives us three building blocks that are used by both template-driven and reactive forms:
- The
FormControl
class: tracks the state of an individual input element. - The
FormGroup
class: tracks the state of a related group of form controls. - The
FormArray
class: tracks state of an array of relatedFormControl
s andFormGroup
s.
So, a form model is composed of instances of FormControl
, FormGroup
and FormArray
classes.
For the signup form above, the form model could look something like this:
FormGroup -> 'signUpFormGroup'
FormControl -> 'fullName'
FormControl -> 'email'
The three fundamental Angular forms' building blocks share a lot of behavior and base functionality that Angular has abstracted out in a class called AbstractControl
. So FormControl
, FormGroup
, and FormArray
are all concrete instances of AbstractControl
.
Learn more about the AbstractControl class in the InDepth.dev blog.
The form model in reactive forms
In the reactive approach, we create the form model ourselves in the component class. For our signup form example, the form model could be created like this:
signUpFormGroup = new FormGroup(
{
fullName: new FormControl(''),
email: new FormControl('')
}
);
We then use reactive forms directives (formControl
, formControlName
, formGroup
, formGroupName
, formArrayname
) to tie the form model to the HTML input elements like this:
<form [formGroup]="signUpFormGroup" (ngSubmit)="submit(signUpFormGroup)">
<label>
Full Name
<input type="text" formControlName="fullName">
</label>
<label>
Email
<input type="email" formControlName="email">
</label>
<button type="submit">Submit</button>
</form>
The form model in template-driven forms
In the template-driven approach, we don't explicitly create a form model. The form model is automagically created by Angular when we use template-driven form directives (ngForm
, ngModel
, and ngModelGroup
) in our HTML like this:
<form #signUpForm="ngForm" (ngSubmit)="submit(signUpForm.form)">
<label>
Full Name
<input type="text" [(ngModel)]="user.fullName">
</label>
<label>
Email
<input type="text" [(ngModel)]="user.email">
</label>
<button type="submit">Submit</button>
</form>
In the template-driven approach, we can still have access to the underlying form model by exporting thengForm
directive into a template reference variable. To do that, we use the#signUpForm="ngForm"
syntax.signUpForm.form
with then contains our form model.
Now that we understand what the form model is, how it's created, and the role it plays in Angular forms, let's talk about what happens when the user interacts with our form inputs in the UI.
DOM events that update FormControl instances
It is important to note that native HTML form elements—<input/>
, <textarea>
, <select>
—always deal with a single value. The user always interacts with individual form elements in UI. Therefore, they are always bound to a FormControl
. We never bind them to a FormGroup
or a FormArray
. Those are logical groupings of FormControl
s. Nothing more.
Whether you're using reactive forms or template-driven forms, Angular keeps in sync the values of the native DOM input elements (the view) and their associated Angular counterparts, the FormControl
instances (the model).
When the view is updated i.e., when the user types a value into the <input/>
element, the <input/>
element emits an input
event. This leads to two main actions on the FormControl
instance:
- the
FormControl
'svalue
property is updated, - the validator functions associated with the
FormControl
are executed.
The problem we are trying to solve with the updateOn option
Here is an example of a FormControl
without the updateOn
option. We've attached some Angular built-in validators—required
and email
—to the control:
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
template: `
<form [formGroup]="signUpFormGroup" (ngSubmit)="submit(emailFormControl)">
<label>
Full Name:
<input [formControl]="emailFormControl" type="email" placeholder="Enter Your Email Address" #inputElement />
</label>
<div *ngIf="emailFormControl.invalid && (emailFormControl.touched || emailFormControl.dirty)">
<div *ngIf="emailFormControl.errors.required">
This field is required
</div>
<div *ngIf="emailFormControl.errors.email">
Please provide a valid email address
</div>
</div>
<button type="submit">Submit</button>
</form>
<pre>
Native Input Element Value: {{ inputElement.value }}
Form Control Value: {{ emailFormControl.value }}
Form Control Status: {{ emailFormControl.status }}
Form Control Touched: {{ emailFormControl.touched }}
Form Control Dirty: {{ emailFormControl.dirty }}
</pre>
`
})
export class AppComponent {
emailFormControl = new FormControl('', {
validators: [Validators.required, Validators.email]
});
signUpFormGroup = new FormGroup({
email: this.emailFormControl
});
submit(emailFormControl: FormControl) {
console.log(emailFormControl);
}
}
Try the live StackBlitz demo:
As you can see, on every keystroke, the state of our form model is updated. This corresponds to the input
event on the DOM <input/>
element being emitted. This DOM input
event is what triggers FormControl
updates. The validator functions get executed and the error messages are updated immediately. So, by default, the validator functions get invoked too often.
When using async validators, the input data is typically sent to a backend server to check its validity. So, you will be sending an HTTP request on every keystroke. Even if your backend is very robust and can handle the charge, this is an unnecessary waste of resources. So, the default validation timing in Angular form is problematic when using server side validation.
We can do better than this!
The updateOn
option in reactive forms
As you can see every time the value of a form control changes, Angular reruns our validators. This can lead to serious performance issues. To alleviate this problem, the v5 release of Angular has introduced the updateOn
property on the AbstractControl
. This means that FormControl
, FormGroup
, and FormArray
, all have this property.
The updateOn
option allows us to set the update strategy of our form controls by choosing which DOM event trigger updates. The possible values for the updateOn
property are:
change
, the default: corresponds to the DOMinput
event of the<input/>
element;blur
: corresponds to the DOMblur
event of the<input/>
element;submit
: corresponds to the DOMsubmit
event on the parent form.
The updateOn
option on a FormControl
To set the updateOn
option on a FormControl
instance, we use the long form of its constructor's second parameter which is of type AbstractControlOptions
:
interface AbstractControlOptions {
validators?: ValidatorFn | ValidatorFn[] | null;
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null;
updateOn?: 'change' | 'blur' | 'submit';
}
Let's see some examples.
Changing the update strategy to 'blur'
Let's now take our above FormControl
example and set the updateOn
property to 'blur'
.
emailFormControl = new FormControl('', {
validators: [Validators.required, Validators.email],
updateOn: 'blur'
});
Try the live Stackblitz demo:
As you can see, updating the <input/>
element won't trigger any validation messages until the blur
event occurs on the <input/>
element i.e., when it loses the focus. This minimizes the number of times our validation functions are executed.
Changing the update strategy to 'submit'
Let's now set the update strategy to 'submit'
for our FormControl
.
emailFormControl = new FormControl('', {
validators: [Validators.required, Validators.email],
updateOn: 'submit'
});
Try the live Stackblitz demo:
Now, neither the input
event nor the blur
even on the <input/>
element will trigger any validation messages. The FormControl
will update itself only when the parent form is submitted.
The updateOn
option on a FormGroup
or a FormArray
FormGroup
and FormArray
are subclasses of AbstractControl
. Therefore, they also support the updateOn
option. If you set the updateOn
property of a FormGroup
or a FormArray
, that value will be used as the default value for the updateOn
property on all its child controls. But, if a child control explicitly sets its own value for the updateOn
option, that explicit value will take precedence.
Let's dissect the following example:
signUpFormGroup = new FormGroup(
{
fullName: new FormControl('', {
updateOn: 'blur'
}),
email: new FormControl('')
},
{ updateOn: 'submit' }
);
Try the live Stackbliz demo:
We use updateOn: 'submit'
on the FormGroup
level and updateOn: 'blur'
on the fullName
FormControl
. With this, the fullName
control will update only when the corresponding input loses focus. For the email
control, the updates only happen when the parent form is submitted.
This is equivalent to the following code:
signUpFormGroup = new FormGroup({
fullName: new FormControl('', { updateOn: 'blur' }),
email: new FormControl('', { updateOn: 'submit' })
});
If your form contains many form controls, setting the updateOn
option on each and every form control can be tedious. This is why the ability to set the updateOn
property on the FormGroup
or FormArray
level is very handy.
It's important to note that the updateOn
property has no impact on when the FormGroup
or FormArray
updates happen. It only affects the child controls. For example, if the update strategy of FormGroup
or FormArray
is set to 'blur'
, and its children use the 'change'
update strategy, the FormGroup
or FormArray
will still update on 'change'
with its children.
Setting the updateOn
option using the FormBuilder
API
The updateOn
option is also available when using the FormBuilder
API to create our form. Below is an example of such usage:
signUpFormGroup = this.fb.group(
{
fullName: this.fb.control('', {updateOn: 'blur'}),
email: this.fb.control('')
},
{updateOn: 'submit'}
);
Dynamically changing the value of the updateOn
option
How can we dynamically the value of the updateOn
option? Angular doesn't provide a way to set the updateOn
option after the form controls has been created. With reactive forms, the only way to set the updateOn
option is in the constructor of the FormControl
, FormGroup
, or FormArray
classes.
I guess this is because dynamically changing the value of the updateOn
option is unlikely to happen, and the Angular team didn't want to add a method setUpdateOn
just for the sake of adding it. If you really want to change the value of the updateOn
option in a form control, your only option is to create a new form control and to replace the previous one.
The updateOn
option in template-driven forms
When using template-driven forms, we don't instantiate directly the form model. Luckily, Angular provides us with the ngModelOptions
on the NgModel
directive that we can use to pass options to the underlying FormControl
that it generates.
The updateOn
option on a ngModel
For example, to set the updateOn
option to 'blur'
, we can do something like this:
<input type="text" [(ngModel)]="user.email" [ngModelOptions]="{updateOn: 'blur'}">
Similarly, this is how we set the update strategy it to 'submit'
in template-driven forms:
<input type="text" [(ngModel)]="user.email" [ngModelOptions]="{updateOn: 'submit'}">
The updateOn
option at the form level
We can also set the updateOn
option at the form level using the ngFormOptions
input of the NgForm
directive. For example:
<form [ngFormOptions]="{updateOn: 'blur'}">...</form>
With this, 'blur'
will be used as the value for the updateOn
option of all the child controls of this form unless they explicitly sets their own value for the updateOn
option using the ngModelOptions
.
Conclusion
In this blog post, we learned about the updateOn
option in both template-driven and reactive forms. We saw that, by default, the form model gets updated too often which can degrade our application's performance if it's heavy on forms validation.
Thanks to the updateOn
option we can use less aggressive update strategies in our Angular forms. This little option can have a huge positive impact in the performance of your Angular applications.
If you want to learn more about Angular forms in general, check out the article "A thorough exploration of Angular Forms" in the InDepth.dev blog.