fbpx Skip to content

Aquent | DEV6

Angular 2: Conditional Validation with Reactive Forms

Written by: James McGeachie

Forms are ubiquitous. Wherever a user has to enter multiple pieces of information to be submitted and processed, a form is generally how it’s done.

Being such a widespread source of data input, validation of forms is crucial. Invalid data cannot be allowed in a database and server-side validation has long been an essential method of preventing this. However, it’s better if the invalid data is never sent to the server in the first place. In a predominantly mobile world, it’s especially important to minimize the number of requests.

Enter client-side validation, where we perform validation in the browser and can immediately display errors to the user when their input is invalid. This is a staple of modern front-end development and as such, the best frameworks are equipped with powerful validation features. Angular 2 is no exception to the rule, as the new Reactive Form Module provides great validation options. Today we’ll cover one aspect of what you can do with it – Conditional Validation.

Basic HTML5 form

Firstly, let’s look at a basic form template without any Angular features.

<form>

  <div>

    <label for="street">Street</label>

    <input name="street" id="street" type="text" required>

  </div>

  <div>

    <label for=“zipcode">Zip Code</label>

    <input name="zipcode" id="zipcode" type="text" pattern="^[0-9]{5}(?:-[0-9]{4})?$" required>

  </div>

  <div>

    <select id="countries" name="countries">

      <option value="US">USA</option>

      <option value="CA">Canada</option>

    </select>

  </div>

  <div>

    <input type="submit" value="Submit">

  </div>

</form>

This code shows a basic HTML5 form for entering an address with a street, zip code and country. Note that we’re using HTML5’s validation attributes to make sure the street and postal code are required, as well as doing a pattern match on zip code to ensure it matches the US zip code format. 

So far so good right? But notice that we have 2 countries in the select box. The other option is Canada. Unlike the US zip codes, Canadian postal codes are 6 characters and alphanumeric. The US zip code regex we used is not applicable. It’d be great if the validation logic was executed when switching countries. This is what we mean by ‘Conditional Validation’. There’s currently no way to do this with simple HTML5 elements & attributes, so we enter JavaScript land for a solution. Let’s look at how to build a reactive form in Angular 2 and how we can handle this.

Reactive Form Set-up

To get started, first you’ll need to import the Reactive Form Module into your project:

import { ReactiveFormsModule } from ‘@angular/forms';

Don’t forget to declare it in your app module imports array.

imports: [

    ReactiveFormsModule

  ]

We’ll now have access to the reactive form syntax we’re going to use within our components. Now let’s look at the set-up for our Address Form Component.

import { Component, OnInit } from '@angular/core';

import { FormBuilder, Validators, FormGroup } from "@angular/forms";


@Component({

    selector: 'address-form',

    templateUrl: ‘address-form-component’,

})

export class AddressForm implements OnInit {

    public addressForm: FormGroup;

    constructor(private formBuilder: FormBuilder) {}

}

At the top of our component file we import the 3 classes we’ll be using within this file – FormBuilder, Validators and FormGroup. 

  • FormBuilder – An Angular 2 injectable that gives us a simple API for creating the form we’ll be interacting with via JavaScript.
  • Validators – Gives us access to commonly-used validation checks via methods
  • FormGroup – Tracks the validity and state of a group of form controls.  

Now that we have our building blocks, let’s initialize our form.

ngOnInit() {

    this.addressForm = this.formBuilder.group({

        street: ['', Validators.required],

        postalCode: ['', [Validators.required, Validators.pattern('^[0-9]{5}(?:-[0-9]{4})?$')]],

        country: ['US']

    });

}

Within the OnInit lifecycle hook, we instantiate our form using formBuilder’s group method, which sets addressForm to a new instance of FormGroup. We give it the 3 properties that will be represented in our form – street, postalCode and country. Each one takes an array where the first item is the initial value of the form input. The next item is an array of validators. We pass in the required validator for both. As we’re setting the default country in the dropdown to US, we also add the pattern Validator to the postalCode array, with the regex for a valid US zip code.

Let’s have a look at the markup that will populate the template file.

<form [formGroup]="addressForm" (submit)="submitForm($event)">

  <div>

    <label for="street">Street</label>

    <input name="street" id="street" formControlName="street" type="text">

  </div>

  <div>

    <label for="postalCode">Postcode</label>

    <input name="postalCode" formControlName="postalCode" id="postalCode" type="text">

  </div>

  <div>

    <select formControlName="country" id="countries" name="countries">

      <option value="US">USA</option>

      <option value="CA">Canada</option>

    </select>

  </div>

  <div>

    <input type="submit" value="Submit">

  </div>

</form>

The main differences from our old HTML5 example are:

  • [formGroup]=“addressForm” – this binds the form to the addressForm FormGroup that we instantiated in our OnInit block.
  • formControlName= “” – for each input, we set the formControlName attribute which associates the value of that input with the FormControls we specified in our addressForm.
  • (submit)=“submitForm($event)” – we bind to the submit event on the form to call the function we wish to use to handle the form submission.

With all of this so far, we have a reactive form that performs the same job as the original basic HTML5 form we illustrated. So what about that validation changing?

Conditional Validation

Angular 2 is built with RXJS and the power of Observables is found throughout the framework. Forms are no different. Each of the form controls on our form group has a ‘valueChanges’ observable that we can subscribe to. 

So, in our OnInit life cycle hook we can also do the following:

this.addressForm.get('country').valueChanges.subscribe(

    (country: string) => {

        if (country === 'US') {

            this.addressForm.get('postalCode').setValidators([Validators.required, Validators.pattern('^[0-9]{5}(?:-[0-9]{4})?$')]);

        } else if (country === 'CA') {

            this.addressForm.get('postalCode').setValidators([Validators.required, Validators.pattern('[ABCEGHJKLMNPRSTVXY][0-9][ABCEGHJKLMNPRSTVWXYZ] ?[0-9][ABCEGHJKLMNPRSTVWXYZ][0-9]')]);

        }

        this.addressForm.get('postalCode').updateValueAndValidity();

    }

)

Now that we’re subscribed to the country input, we can check its new value when it changes and handle it accordingly. Here, we’re calling the ‘setValidators’ method on the postalCode form control to set new validators depending on the state of ‘country’. We set the appropriate regex for a Canadian postal code when the country is CA. At the end we call the ‘updateValueAndValidity’ method to apply the new validation checks to the field. This way we can immediately show if the field is now invalid.

Next Steps

Now that you have conditional validation in-place, you can go ahead and add error messages in the template that display when the input is invalid. Then you can watch the postal code error message appear when you switch country and be relieved that Angular 2 has made this process so simple!

For further details, the Angular 2 guide on Reactive Forms is a great source. It covers topics such as nested FormGroups and FormArrays that were not covered in this post. https://angular.io/docs/ts/latest/guide/reactive-forms.html

Also, be sure to keep checking back for further Dev6 blogs on this topic in the future.

Sign up for our Angular Course

Learn the most recent version and start building your own Angular apps

View Course Details