Form validation with Angular's FormBuilder class

January 26, 2017, 8:29 pm Categories:

Categories

** UPDATED FOR IONIC 3 **

Introduced with Angular 2 the FormBuilder module provides developers with an API to programmatically define validation rules for forms in their apps using pre-existing rules and/or through scripting their own custom logic.

Over the course of this tutorial I'm going to take you through using the FormBuilder API in your projects and, as with most things Angular2/Ionic 2, we'll be taking a close look at how the component class and the related HTML work together in order to accomplish this.

Using the FormBuilder

In its most basic implementation the FormBuilder API would be used in a component class (for this example I'm using the HomePage component) like so:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import {  
	FormBuilder, 
	FormGroup,  
  	Validators 
} from '@angular/forms';


@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

   public form   : FormGroup;

   constructor(public navCtrl  : NavController,
               private _FB     : FormBuilder) 
   {
      this.form 	 = _FB.group({
         'name'        : ['', Validators.required],
         'message'     : ['', Validators.minLength(10)]
      });
       
   }

}

It's a relatively basic script but there are a couple of key areas that we need to focus on and understand.

Our script starts with importing the following Angular 2 modules from the @angular/forms directory:

  • FormBuilder
  • FormGroup
  • Validators

Each of the above provide very specific form related functions and utilities that we'll use to implement validation rules with.

With that in mind let's take a look at each of these modules so we understand their purpose and how they fit together collectively.

FormBuilder

The FormBuilder module is essentially a high-level manager that allows individual FormGroup's and FormControls to be composed and collectively structured through a single, unified API.

In the above code snippet the FormBuilder module is injected as a dependency into the HomePage constructor:

constructor(private navCtrl 	: NavController,
  	        private _FB 		: FormBuilder) 

This is then used to create a FormBuilder object within the constructor like so:

this.form 	 = _FB.group({
   'name'        : ['', Validators.required],
   'message'     : ['', Validators.minLength(10)]
});

This FormBuilder object contains individual FormControls assigned to the following keys:

  • name
  • message

These represent the HTML form input fields that they will be assigned to.

Each FormControl has a specific Validators rule associated with them (more on these shortly).

We assign the FormBuilder object to a FormGroup object named form (how original!) which we will subsequently reference in the HTML form for the component.

FormGroup

A FormGroup is used to create a programmatic representation of the DOM structure of our form using individual FormControls which are associated with their respective HTML input elements.

In the initial code example we created a FormGroup object with the following line:

public form : FormGroup

This is then used to assign the FormBuilder object, with its FormControls and associated Validators like so:

this.form 	 = _FB.group({
   'name'        : ['', Validators.required],
   'message'     : ['', Validators.minLength(10)]
});

Each FormControl is structured with the following:

  • An empty string (which could be pre-filled with a default value is we so desired)
  • The validation rule that we want to assign for use with the HTML input field that this FormControl maps to

We'll see how each FormControl maps to its assigned HTML input field a little later on in the tutorial.

One really nice feature of using the FormBuilder API is that we can also nest FormGroup's should our form validation logic required that.

For example, if we wanted to validate whether at least one of a series of checkboxes in our form had been selected we might use something like the following:

this.form 	 = _FB.group({
   'name'        : ['', Validators.required],
   'message'     : ['', Validators.minLength(10)],
   'skills'      : _FB.group({
      'front-end': [false],
      'back-end' : [false],
      'mobile'   : [false]    
   }, { validator: _VAL.validateCheckboxes });
});

Notice the validator at the end of our nested group?

{ validator: _VAL.validateCheckboxes }

This references a custom validation rule that we can provide to the FormBuilder object.

Validators

Currently Angular 2 provides the following built-in Validators:

  • required - declares that a form control cannot have an empty value
  • minlength - declares the minimum length of data required for that form control
  • maxlength - declares the maximum length of data required for that form control
  • pattern - declares a regular expression pattern that data entered into the form control must pass

In and of themselves these rules would be sufficient for basic forms within an app but what if you required something a little more specific, say a dedicated rule for validating an e-mail address (which you could then re-use in different forms throughout your app - should the need exist)?

Here's where the FormBuilder API really shines by allowing developers to write their own validation rules.

As one of the goals of development should be concerned with creating modular, reusable code I always implement custom validation rules within their own dedicated angular 2 service (or, depending on your terminology preference, you may refer to these as a Provider or Injectable).

So, using the Ionic CLI, I would create a service specifically for handling form validation rules like so (I've chosen the name Validator as it's fairly self-explanatory but you could name the service however you see fit):

ionic g provider Validator

Within this service I would then code the specific types of validation rules that I want to implement (in the following snippet I have created a single method named validateEmail which - surprise, surprise - will be used for validating e-mail addresses):

import { Injectable } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';


@Injectable()
export class Validator {

  constructor(public http: Http) 
  {

  }


   validateEmail(control: FormControl) 
   { 
      return new Promise(resolve => 
      {
         let emailPattern = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
         if(!emailPattern.test(control.value))
         {
            resolve({ InvalidEmail : true });
         } 
         resolve(null); 
      });
   }


   // Other methods listed here
  	
}

Here you can see I've imported the FormControl module which allows me to inject that as a parameter for validation methods that I create in this service.

// Here we import the FormControl module
import { FormControl } from '@angular/forms';


// And, subsequently, we inject this module as a parameter to our validation method
validateEmail(control: FormControl)
{
  // Logic here
}

Injecting the FormControl into a custom method allows that method, when used to supply validation rules in the FormBuilder object, to receive data from the form input field that the FormControl is mapped to.

Pretty handy huh?

The validateEmail method uses a Promise to handle returning the result of testing the supplied FormControl value against a regular expression pattern to determine whether a match was found or not. You'll notice from the above that we return a value of null if the pattern matches. This is parsed by the FormBuilder API as the validation having been successfully passed.

This custom method would then be implemented in a number of steps starting with importing the Validator service and declaring that within the providers array of the app.module.ts file like so:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { Validator } from '../providers/validator';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, Validator]
})
export class AppModule {}

Following from this we would then:

  • Import the Validator provider in the component class where we wish to use that
  • Initialise the Validator provider in the class constructor
  • Supply the validateEmail method from the Validator provider as a validation rule on a specified FormControl

All of which might look something like the following:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import {
	FormBuilder,
	FormGroup,
  	Validators
} from '@angular/forms';
import { Validator } from '../../providers/validator';


@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

   public form   : FormGroup;

   constructor(public navCtrl  : NavController,
               private _FB     : FormBuilder,
               private _VAL    : Validator)
   {
      this.form 	 = _FB.group({
         'name'        : ['', Validators.required],
         'email'       : ['', _VAL.validateEmail],
         'message'     : ['', Validators.minLength(10)]
      });

   }

}

There's a few steps involved with this but, as you can see, supplying your own custom validation rules to the FormBuilder object is fairly straightforward.

So far we've covered the following aspects of implementing the FormBuilder API:

  • Creating a FormGroup object
  • Create a FormBuilder object
  • Creating individual FormControl objects
  • Implementing built-in validation rules
  • Implementing custom validation rules

All of which is great but there's one question that immediately springs to mind: how is the component logic able to interact with the actual HTML form?

HTML Forms with the FormBuilder API

With our FormBuilder object and its validation rules declared in the component class we can now use the FormGroup object to "plug" this into the component HTML form and assign each FormControl to an input field in the DOM.

Doing so enables the class to communicate with the HTML form and vice-versa - all of which is facilitated through the following steps:

  • Assigning a formGroup property, with the name of the FormGroup object that we created in the component class, to the HTML form
  • Assigning formControlName attributes to the HTML form input fields

With this in mind here's what our HTML form might look like:

<form [formGroup]="form" (ngSubmit)="saveDetails(form.value)">
   <ion-list>
      <ion-item margin-bottom>
         <ion-label>Your Name</ion-label>
         <ion-input type="text" formControlName="name"></ion-input>	    	
      </ion-item>

      <ion-item margin-bottom>
         <ion-label>Your Email address</ion-label>
         <ion-input type="email" formControlName="email"></ion-input>
      </ion-item>

      <ion-item margin-bottom>
         <ion-label>Your Message</ion-label>
         <ion-textarea formControlName="message"></ion-textarea>
      </ion-item>

		  	
      <ion-item margin-bottom>
         <button ion-button color="primary" 
            text-center 
            block 
            [disabled]="!form.valid">Send your message</button>
      </ion-item>

   </ion-list>
</form>

There are 3 things we need to pay attention to here.

Firstly, the inclusion of the formGroup property which links the FormBuilder object, and all its associated validation rules, that we created in our component class with the HTML form:

<form [formGroup]="form" (ngSubmit)="saveDetails(form.value)">

Secondly, each individual input element in our form has a formControlName attribute assigned to it.

This attribute contains a value matching the key of a specific FormControl defined in the FormBuilder object in our component class:

<ion-input type="text" formControlName="name"></ion-input>	    	


<ion-input type="email" formControlName="email"></ion-input>


<ion-textarea formControlName="message"></ion-textarea>

Finally we assign a disabled property to the form submit button to disallow the form from being able to be submitted until it has passed all validation checks:

<button ion-button 
   color="primary" 
   text-center 
   block 
   [disabled]="!form.valid">Send your message</button>

Disabling the button isn't technically necessary as we could simply use the FormBuilder API to return back the results of the validation checks and then inform the user of these.

By disabling the button though we are helping to prevent inaccurate form submissions - not to mention that it's also a useful UX convention.

Thanks to some very basic modifications to the form HTML though we can implement and leverage the FormBuilder API to handle all required validation.

This is both quick and simple to accomplish and, best of all, we don't have to worry about using ID's or classes as hooks to interrogate the DOM with - thanks to the FormBuilder API we can simply use FormControls to bind the form input fields to a FormGroup object instead.

In summary

Thanks to Angular's FormBuilder API developers can now programmatically assign existing and/or custom validation rules to their forms.

These validation rules, supplied through the use of Validators, are assigned to individual FormControl objects which allow the component class to 'talk' with the HTML form and its target fields.

Essentially a FormControl acts as an interface, or bridge, between the component class and the input element to which it is bound; allowing form data to be accessed without having to interrogate the DOM (as we might, for example, do using selectors in jQuery or vanilla JavaScript).

The validator assigned to that FormControl would then check to see if the FormControl has any associated data and whether or not the criteria for the validation rule has been met.

And with that we conclude this tutorial on using the FormBuilder API in your Ionic applications.

There was a lot to take in but, once you start working with this API, you'll find it's actually quite straightforward and relatively quick to implement.

Feel free to use the above code in your own Ionic projects as you see fit and, if you're feeling generous, consider using the form below to leave a comment on this tutorial.

If you enjoyed what you've read here then please sign up to my mailing list and, if you haven't done so already, take a look at my e-book: Mastering Ionic for further information and examples of using forms with the Ionic framework.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top