What is the updateOn Option in Angular Forms
Validating user input is a vital part of working with forms. It stops users from sending forms that are incomplete or incorrect, sparing them the annoyance of repeatedly resubmitting the same flawed form. In Angular, validation is implemented through functions that receive user input and inform us whether the data entered is valid or invalid.
By default, form control values are refreshed with every keystroke. The validation functions are then run as well. This behavior may not always be ideal. At times, we want more precise command over when value updates and validators are triggered. The updateOn option in Angular forms exists precisely for this purpose.
This piece clarifies what the updateOn option is, why it matters, and how to apply it so that we don't harm the performance of our Angular applications.
To grasp the issue and the benefits offered by updateOn, we first need to understand the mechanics of Angular forms. Consider a simple HTML form in our interface with no Angular at all:
<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 has two inputs along with a submit button. The first input, fullName, is for the user's full name, while the second input, email, captures the user's email address. How do we manage such a form using Angular?
Two approaches, a single objective
Handling this form in Angular offers two distinct methods: template-driven and reactive. The template-driven style puts form responsibilities inside the HTML template, whereas the reactive style vests control in the component class. These methods differ fundamentally in how the developer wires up the form. However, internally they rely on identical foundational pieces and accomplish the same objective, which is monitoring:
- the value entered by the user in the UI: the form's fundamental purpose—the data we aimed to collect by displaying the form;
- how the user interacts with the form inputs;
- whether any errors are present on the form inputs.
To reach this objective, Angular provides components that define a form model. So, what exactly is a form model, and what are the components that constitute it? Let's look into it!
The form model
The form model is the underlying structure Angular uses to represent an HTML form. It serves as the connection between HTML form elements and Angular. To construct this form model, Angular supplies three components that both template-driven and reactive forms rely on:
- The
FormControlclass: governs the state of a single input element. - The
FormGroupclass: governs the state of a collection of related form controls. - The
FormArrayclass: governs the state of an array of relatedFormControls andFormGroups.
Thus, a form model consists of instances of the FormControl, FormGroup, and FormArray classes.
For the signup form shown above, the form model might look like the following:
FormGroup -> 'signUpFormGroup'
FormControl -> 'fullName'
FormControl -> 'email'
These three core Angular form components share substantial behavior and basic functionality that Angular has consolidated into a class named AbstractControl. Both FormControl, FormGroup, and FormArray are concrete implementations of AbstractControl.
Explore more about the AbstractControl class in the InDepth.dev blog.
The form model in reactive forms
When following the reactive style, we build the form model ourselves within the component class. For our signup form, the form model could be created in this manner:
signUpFormGroup = new FormGroup(
{
fullName: new FormControl(''),
email: new FormControl('')
}
);
Next, reactive form directives (formControl, formControlName, formGroup, formGroupName, formArrayname) connect 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
With the template-driven style, we don't explicitly build a form model. Angular creates the form model automatically when we employ template-driven directives (ngForm, ngModel, and ngModelGroup) in our HTML, for instance:
<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>
Even in the template-driven style, we can access the underlying form model by exporting the
ngFormdirective into a template reference variable. This is done with the#signUpForm="ngForm"syntax. From there,signUpForm.formholds our form model.
With a clear picture of the form model, how it's built, and its function in Angular forms, let's examine what takes place when the user interacts with our form inputs.
DOM events that trigger FormControl updates
An important point is that native HTML form elements—<input/>, <textarea>, <select>—always handle a single value. Users interact with one form element at a time. For this reason, each is always connected to a FormControl. They are never linked to a FormGroup or a FormArray. Those are simply logical groupings of FormControls.
Whether you opt for reactive or template-driven forms, Angular ensures that the values of the native DOM input elements (known as the view) and their respective Angular counterparts, the FormControl instances (known as the model), remain in sync.
Upon a view update—such as the user typing into the <input/> element—the <input/> element dispatches an input event. This leads to two key actions on the FormControl instance:
- the
FormControl'svalueproperty gets refreshed, - the validator functions linked to the
FormControlare run.
The issue that updateOn aims to resolve
Here's an example of a FormControl lacking the updateOn option. We've connected Angular's 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);
}
}
You can view the live StackBlitz demo:
As observed, the state of our form model refreshes on every keystroke. This corresponds to the input event from the DOM <input/> element. This DOM input event is what causes FormControl updates. The validation functions are executed, and error messages refresh immediately. In essence, by default, the validators run far too frequently.
With async validators, the user-entered data is typically transmitted to a backend server for validation. Consequently, you'd be dispatching an HTTP request for every keystroke. Even with a robust backend that can manage the load, this represents an avoidable drain on resources. Therefore, the default validation timing in Angular forms becomes problematic for server-side checks.
We can improve upon this!
The updateOn option in reactive forms
As shown, each time a form control's value shifts, Angular re-runs our validators. This can result in significant performance degradation. To counter this, Angular v5 launched the updateOn property on AbstractControl. This indicates that FormControl, FormGroup, and FormArray all possess this property.
The updateOn option let's us define the update strategy for our form controls by selecting which DOM event triggers the updates. The valid values for the updateOn property are:
change, the default: aligns with the DOMinputevent of the<input/>element;blur: aligns with the DOMblurevent of the<input/>element;submit: aligns with the DOMsubmitevent on the parent form.
Applying updateOn to a FormControl
To set the updateOn option for a FormControl instance, we employ the extended version of its constructor's second parameter, which has the type AbstractControlOptions:
interface AbstractControlOptions {
validators?: ValidatorFn | ValidatorFn[] | null;
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null;
updateOn?: 'change' | 'blur' | 'submit';
}
Let's examine a few examples.
Switching to the 'blur' update strategy
Let's take our earlier FormControl example and assign the updateOn property the value 'blur'.
emailFormControl = new FormControl('', {
validators: [Validators.required, Validators.email],
updateOn: 'blur'
});
You can view the live Stackblitz demo:
Notice that typing into the <input/> element fails to produce any validation messages until a blur event fires on that element—meaning when it loses focus. This cuts down the frequency at which our validation functions get called.
Switching to the 'submit' update strategy
Now, let's configure the update strategy as 'submit' for our FormControl.
emailFormControl = new FormControl('', {
validators: [Validators.required, Validators.email],
updateOn: 'submit'
});
You can view the live Stackblitz demo:
In this case, neither the input event nor the blur event on the <input/> element will bring about any validation messages. The FormControl will only refresh when the containing form is submitted.
Applying updateOn to a FormGroup or a FormArray
FormGroup and FormArray derive from AbstractControl. Thus, they also accommodate the updateOn option. When you assign the updateOn property of a FormGroup or a FormArray, that value serves as the default for the corresponding property on every child control. However, when a child control defines its own updateOn value explicitly, that explicit setting wins.
Let's break down the following example:
signUpFormGroup = new FormGroup(
{
fullName: new FormControl('', {
updateOn: 'blur'
}),
email: new FormControl('')
},
{ updateOn: 'submit' }
);
You can view the live Stackbliz demo:
We set updateOn: 'submit' at the FormGroup level and updateOn: 'blur' on the fullName FormControl. As a result, the fullName control refreshes only when its respective input loses focus. As for the email control, updates occur solely at the point when the parent form is submitted.
This setup corresponds exactly to the following code:
signUpFormGroup = new FormGroup({
fullName: new FormControl('', { updateOn: 'blur' }),
email: new FormControl('', { updateOn: 'submit' })
});
When your form contains a large number of fields, applying the updateOn option individually to each form control becomes repetitive. That's why being able to define the updateOn property at the FormGroup or FormArray level proves quite useful.
Keep in mind that the updateOn property does not constrain when the FormGroup or FormArray itself refreshes. Its scope is limited to the child controls. For instance, if the update strategy for a FormGroup or FormArray is 'blur', while its children adopt a 'change' strategy, the FormGroup or FormArray will still update on 'change' alongside its children.
Using the FormBuilder API to set updateOn
The updateOn option is also accessible when constructing our form with the FormBuilder API. Below is an example of such usage:
signUpFormGroup = this.fb.group(
{
fullName: this.fb.control('', {updateOn: 'blur'}),
email: this.fb.control('')
},
{updateOn: 'submit'}
);
Dynamically altering the updateOn value
How can we dynamically adjust the updateOn value? Angular lacks a built-in mechanism to set the updateOn option once the form controls have been instantiated. With reactive forms, the updateOn value must be specified in the constructor of the FormControl, FormGroup, or FormArray classes.
This is likely due to the fact that changing the updateOn value on the fly is an uncommon need, and the Angular team chose not to add a setUpdateOn method purely for that purpose. If you do need to modify the updateOn value on a form control, the sole option is to generate a new form control and swap it in place of the original.
Applying updateOn in template-driven forms
In template-driven forms, the form model is not created manually. Instead, Angular exposes the ngModelOptions directive on NgModel, which lets you pass configuration directly to the FormControl it instantiates behind the scenes.
Setting updateOn on a ngModel
To configure the updateOn option with a value of 'blur', the following markup is used:
<input type="text" [(ngModel)]="user.email" [ngModelOptions]="{updateOn: 'blur'}">
In the same way, switching the update strategy to 'submit' in a template-driven form is done like this:
<input type="text" [(ngModel)]="user.email" [ngModelOptions]="{updateOn: 'submit'}">
Defining updateOn at the form level
It is also possible to define the updateOn option for an entire form using the ngFormOptions input on the NgForm directive. For instance:
<form [ngFormOptions]="{updateOn: 'blur'}">...</form>
As a result, all child controls within this form will inherit 'blur' as their updateOn value, unless a particular control overrides it by supplying its own updateOn through ngModelOptions.
Wrapping up
Throughout this post, we examined the updateOn option as it applies to both reactive and template-driven forms. The default behavior updates the form model frequently, which can be a drag on performance in forms with heavy validation logic.
With the updateOn option, you can adopt less frequent update triggers. Even a small adjustment here can yield significant performance improvements across your Angular applications.
For a broader look at Angular forms, refer to the article "A thorough exploration of Angular Forms" available on the InDepth.dev blog.
