DEV Community

Preston Lamb
Preston Lamb

Posted on • Originally published at prestonlamb.com on

Reactive Forms Benefits: Validators

tldr;

Form validation is an important part of the process when getting information from people in an application. We want to make sure that they don’t submit incorrect data, like a string for a number input. There are some built-in validators in browsers, but we can easily add custom validators to form inputs when we use reactive forms. The custom validators can be for individual input fields or for the whole form, and either way they’re easy to add and apply to forms.

What are Reactive Forms?

Before we jump in to using custom validators on our reactive forms, let’s review what reactive forms in Angular even are. The Angular docs say this about reactive forms:

Reactive forms provide a model-driven approach to handling form inputs whose values change over time. … Reactive forms use an explicit and immutable approach to managing the state of a form at a given point in time. Each change to the form state returns a new state, which maintains the integrity of the model between changes. Reactive forms are built around observable streams, where form inputs and values are provided as streams of input values, which can be accessed synchronously.

In other words, we declare the values we want to capture in our form inside the component class. Also in the component class we can subscribe to form value changes and do something each time a value is changed. Each time the form is changed, an entirely new state object for the form is emitted, making sure that the form data is immutable.

The syntax for creating a form control to capture some piece of data is as follows:

{
    formControlName: [<initialValue>, [<syncValidators>], [<asyncValidators>]]
}

We can add multiple key/value pairs when creating the formGroup for our reactive form. The only required value is the initialValue. Below is an example of creating a formControl with only the initialValue. Later on we’ll see examples of creating form controls with both a single synchronous validator as well as multiple synchronous validators. We won’t cover asynchronous validators in this article, but they’re very similar to synchronous validators. The only difference is that they return an Observable or a Promise.

Alright, let’s go ahead and get started! Here’s an example of how to create a reactive form. This is just the constructor and ngOnInit functions of a component class:

constructor(private _formBuilder: FormBuilder) {}

ngOnInit() {
    this.form = this._formBuilder.group({
        username: '',
        password: ''
    })
}

This creates two inputs that we will capture, a username and password. The empty quotes means that the starting value of the form is an empty string. If we wanted to set a default value, we could place that value inside the quotes. When outputting the form, it’ll look like this:

<form [formGroup]="form">
    <input type="text" formControlName="username" />
    <input type="password" formControlName="password" />
</form>

It’s very similar to a normal, template-driven form, with two main differences. First, the form that we created in the ngOnInit function should be passed in to the [formGroup] input of the form. Next, each input (or textarea) should have an attribute of formControlName where the value matches one of the values declared in the class file.

Built-in Validators

One last thing before we get to custom validators on reactive forms. There are a bunch of built-in validators that Angular provides for us. You can get to them by importing Validators from the @angular/forms module. You can see the list of built-in validators here. A few of them are min, max, and required. Here’s the form creation changed to show how to add validators:

ngOnInit() {
    this.form = this._formBuilder.group({
        username: ['', Validators.required],
        password: ['', Validators.required]
    })
}

In the example above, we changed the inputs to be required. The default value is still an empty string, but before the field is valid we will need to add something to the input. We can add more than one validator like this:

ngOnInit() {
    this.form = this._formBuilder.group({
        username: ['', [Validators.required, Validators.minLength(3)]],
        password: ['', [Validators.required, Validators.minLength(7)]]
    })
}

Now we’ve added a minLength validator to each input. The username must be at least 3 characters long, and the password field at least 7 characters long. The only thing to remember when adding validators is that if we’re adding a single validator, we can add it as the second value in the array when declaring a form attribute. If we’re adding more than one validator, the second value in the array should be an array of validators.

Custom Validators

Okay, so now we know what reactive forms are, and how to use the built-in validators provided by Angular. Let’s look now at how to create a custom validator and how to use it. In its simplest form, a validator for a reactive form is a function that returns whether the field should have an error applied to it or not. If there is an error, we can return an object whose key describes the error, and the value of true. You could return a different value, but this is the way I’ve seen it implemented most frequently.

Here’s an example of a custom validator to ensure that the input is a number. The number can be a decimal (.01 for example) for a whole number. Here’s the validator function:

function number(control: AbstractControl): ValidationErrors | null {
    const NUMBER_REGEX = /^([0-9])*([\.])?[0-9]+$/;
    return NUMBER_REGEX.test(control.value) ? null : { number: true };
}

This function takes an AbstractControl as the parameter, from which we can get the value of the control. It uses a regular expression that tests the value to make sure it’s a number. If it is a number, the function returns null (meaning there is no error). If it is not a number, we return an object that has a key of number and a value of true, setting the number error to true. To add the validator to a form control, we would define our form like this:

ngOnInit() {
    this.form = this._formBuilder.group({
        paymentAmount: [0, [Validators.required, Validators.min(0.01), CustomValidators.number]]
    })
}

In this declaration, the CustomValidators.number validator would be the third to be checked on the control.

CustomValidators is the class or object that we’re exporting with our custom validator functions to use on forms. You can organize them and export them for use in your application any way that works for you.

Any validator that needs to be run on a single form input value will follow that pattern. But some validation requires information from more than one input. An example of this would be making sure that two fields have the same value (like when confirming a password). Here’s an example of a validator function that compares the password field with the confirm password field. If they are the same, everything’s good. If not, an error will be set.

function passwordsMatch(formGroup: FormGroup) {
    const password = formGroup.get('password');
    const confirmPassword = formGroup.get('confirmPassword');

    if (!password || !confirmPassword || !password.value || !confirmPassword.value) {
        return null;
    }
    if (password.value !== confirmPassword.value) {
        confirmPassword.setErrors({ confirmPassword: true });
    } else {
        confirmPassword.setErrors(null);
    }
}

In this function, the formGroup is passed in to the function, and then we get the password and confirmPassword controls. If either control doesn’t exist, or either value doesn’t exist, the function just returns. After that, if the values don’t match, an error is set on the confirmPassword control. If they match, however, we clear the errors on the field.

We can add this validator to the form in this manner:

ngOnInit() {
    this.form = this._formBuilder.group({
        username: ['', [Validators.required, Validators.minLength(3)]],
        password: ['', [Validators.required, Validators.minLength(7)]],
        confirmPassword: ['', [Validators.required, Validators.minLength(7)]]
    },
    {
        validators: [CustomValidators.passwordsMatch]
    })
}

FormGroup level validators are added as a second object parameter to the FormBuilder.group function.

Conclusion

In review, reactive forms in Angular are forms that are declared in a component’s class file, and which allow us to subscribe to immutable state of the form and react accordingly. We also can easily add custom validation on single fields as well as multiple fields combined in the form. Reactive forms are a little bit of a change to traditional template driven forms, but there are some distinct benefits to using them. Keep an eye out in the future for more articles on the benefits of using reactive forms in Angular!

Top comments (0)