Author avatar

Yallaling Goudar

Validating Reactive Forms in Angular

Yallaling Goudar

  • Aug 2, 2019
  • 9 Min read
  • 773 Views
  • Aug 2, 2019
  • 9 Min read
  • 773 Views
Languages Frameworks and Tools
Angular

Introduction

In this guide, we will be exploring the Angular reactive form validation. Form validation, in general, will help us to improve the overall data quality by validating user input for accuracy and completeness. In this guide, we will see how to validate the user input in the UI and display useful validation messages using reactive forms.

Reactive Form Validation in Angular

In template-driven forms, we will add validators through attributes. But in reactive forms, we add validator functions directly to form a control model in the component class. Angular then calls these functions whenever the value of the control changes.

Validator Functions in Reactive Forms

There are basically two types of validator functions:

  • Sync validators: Sync validators are functions that will take a control instance and return either a set of validation errors or null immediately. We can pass these sync validator functions in as the second argument when you instantiate a FormControl.
  • Async validators: Async validators are functions that will take a control instance and return a Promise or Observable that will later emit a set of validation errors or null. We can pass these async validator functions in as the third argument when you instantiate a FormControl.

Built-in Validators in Reactive Forms

In forms, we can either use our own validator functions or some of the Angular’s built-in validators.

The built-in validator functions are the same attributes available for both the template-driven forms and the reactive forms, such as required and minlength, are all available to use as functions from the Validators class.

Filename: employee-form.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ngOnInit(): void {
  this.employeeForm = new FormGroup({
        	'employeename': new FormControl(this.employee.name, [
        	Validators.required,
        	Validators.minLength(8),
   	forbiddenEmployeeNameValidator(/yash/i)
        	]),
        	'address': new FormControl(this.employee.address),
        	'company': new FormControl(this.employee.company, Validators.required)
  });
 
}
 
get employeename() { return this.employeeForm.get('employeename'); }
 
get company() { return this.employeeForm.get('company'); }
typescript

As we can see in the above example, the employeename control sets up two built-in validators, Validators.required and Validators.minLength(8), and one custom validator, forbiddenEmployeeNameValidator. The above example adds a few getter methods. In a reactive form, you can always access any form control through the get method on its parent group, but sometimes it's useful to define getters as shorthands for the template. Let’s now look at the template for the employeename input.

Filename: employee-form.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<input id=" employeename " class="form-control"
        	formControlEmployeeName =" employeename " required >
 
<div *ngIf=" employeename.invalid && (employeename.dirty || employeename.touched)"
        	class="alert alert-danger">
 
  <div *ngIf=" employeename.errors.required">
        	EmployeeName is required.
  </div>
  <div *ngIf=" employeename.errors.minlength">
        	EmployeeName must be at least 8 characters long.
  </div>
  <div *ngIf=" employeename.errors.forbiddenEmployeeName">
        	EmployeeName cannot be yash.
  </div>
</div>
typescript

Adding Custom Validators to Forms

In some cases, we may want to create the custom validators based on our needs, as the built-in validators won’t always match the exact needs of your application. Let’s consider an example where we are creating the forbiddenEmployeeNameValidator custom validator.

Filename: forbidden-employeename.directive.ts

1
2
3
4
5
6
export function forbiddenEmployeeNameValidator(employeenameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    const forbidden = employeenameRe.test(control.value);
    return forbidden ? {'forbiddenEmployeeName': {value: control.value}} : null;
  };
}
typescript

The function here is actually a factory that takes a regular expression to detect a specific forbidden employeename and returns a validator function. In the above example the forbidden employeename is “yash”. The validator will reject any employee name containing “yash”.

The forbiddenEmployeeNameValidator factory returns the configured validator function. That function takes an Angular control object and returns either null, if the control value is valid, or a validation error object. The validation error object typically has a property whose name is the validation key, 'forbiddenEmployeeName', and whose value is an arbitrary dictionary of values that you could insert into an error message, {employeename}.

Custom async validators are similar to sync validators, but they must instead return a Promise or Observable that later emits null or a validation error object. In the case of an Observable, the Observable must complete, at which point the form uses the last value emitted for validation.

Adding Custom Validators to Reactive Forms

To add custom validators in reactive forms all we have to do is pass the function directly to the FormControl as shown in the below code example.

Filename: employee-form.component.ts

1
2
3
4
5
6
7
8
9
this.employeeForm = new FormGroup({
  'employeename': new FormControl(this.employee.name, [
        	Validators.required,
        	Validators.minLength(8),
 	forbiddenEmployeeNameValidator(/yash/i)
  ]),
  'employeeSurname': new FormControl(this.employee. 'employeeSurname'),
  'company': new FormControl(this.employee.company, Validators.required)
});
typescript

Control Status CSS Classes in Angular

Angular automatically contains many control properties for the form control element as CSS classes. We can use these classes to style form control elements according to the state of the form. The following classes are currently supported in the CSS:

  • .ng-valid
  • .ng-invalid
  • .ng-pending
  • .ng-pristine
  • .ng-dirty
  • .ng-untouched
  • .ng-touched

Below are the examples of the styles that we can use in our applications like .ng-valid and .ng-invalid.

Filename: forms.css

1
2
3
4
5
6
7
.ng-valid[required], .ng-valid.required  {
  border-left: 6px solid green;
}
 
.ng-invalid:not(form)  {
  border-left: 6px solid red;
}
css

Cross-Field Validation in Reactive Forms

In this section, we will see how to perform cross-field validation in the reactive forms in Angular. Now, let’s consider an example where the employeename and address are sibling controls. To evaluate both controls in a single custom validator, we should perform the validation in a common ancestor control: the FormGroup. That way, we can query the FormGroup for the child controls which will allow us to compare their values. In the below example, to add a validator to the FormGroup, pass the new validator in as the second argument on creation.

1
2
3
4
5
const employeeForm = new FormGroup({
  'employeename': new FormControl(),
  'address': new FormControl(),
  'company': new FormControl()
}, { validators: revealIdentityValidator });
typescript

The validator code is as follows:

Filename: reveal-identity.directive.ts

1
2
3
4
5
6
export const revealIdentityValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
  const employeename = control.get('employeename');
  const address = control.get('address');
 
  return employeename && address && employeename.value === address.value ? { 'revealIdentity': true } : null;
};
typescript

The identity validator implements the ValidatorFn interface. It takes an Angular control object as an argument and returns either null if the form is valid, or ValidationErrors otherwise. First, we retrieve the child controls by calling the FormGroup's get method. Then we simply compare the values of the employeename and address controls. If the values do not match, the employee's identity remains secret, and we can safely return null.

Now let’s see how we can show an appropriate error message when the form in invalid, so that we can provide a better user experience.

Filename: employee-form.component.html

1
2
3
<div *ngIf="employeeForm.errors?.revealIdentity && (employeeForm.touched || employeeForm.dirty)" class="cross-validation-error-message alert alert-danger">
        	EmployeeName cannot match address.
</div>
html

Conclusion

In this guide, we have explored reactive form validations in Angular. We have also seen how we can build or create a custom reactive form validation and use it in our application. You can learn more about Angular Template Driven Forms validations in my guide Validating Template Driven Forms in Angular.

3