Validating multiple checkboxes with Ionic

January 28, 2017, 9:53 pm Categories:

Categories

** UPDATED FOR IONIC 3 **

Forms are rarely the most enjoyable part of developing applications but thanks to Angular's FormBuilder API the sting of validating input fields is greatly reduced.

Why the FormBuilder API rocks

Instead of having to write multiple DOM selectors, conditional logic and code validation rules from scratch, as we might well find ourselves doing with vanilla JavaScript or jQuery, we can use a combination of FormGroups, FormControls and Validators to programmatically handle validating different input fields in our application forms.

This gives us a huge amount of leverage in the following ways:

  • Template HTML and validation logic are kept cleanly separated (leading to less problems in managing code)
  • Pre-supplied Validators allow developer's to quickly scaffold validation logic for their applications
  • Input fields are abstracted through the use of FormControls allowing the component class to communicate with the template HTML, and vice versa
  • Validation logic is handled through an extensible, modular API which can be plugged into with custom rules where required
  • FormGroup's can be nested, allowing the development of more complex validation rules where necessary

What we'll be building

Developing from what was discussed in the last section I want to demonstrate how we can validate multiple checkboxes within an Ionic 2 form (as this is both one of those trickier validation requirements and we might also want to make sure that the user makes at least one selection from the available options).

To do so we'll develop a simple application, as displayed in the following screen capture, that allows a user to select their favourite social media networks in addition to entering a message.

Ionic 2 app with multiple checkbox options

We'll be using pre-built Ionic UI components, add custom validation logic and implement a character counter for the message field.

Ready?

Okay then, let's get cracking!

Building the app

Using the Ionic CLI create the following application:

ionic start form-validator blank

Once completed change into the project directory and issue the following command:

ionic g provider Validator

Here we're creating an Angular 2 service which we'll use to store and manage the custom validation logic for our application.

Open the newly created service, located at src/providers/validators.ts, and implement the following code:

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) 
   {

   }


   validateCheckboxes(boxes: FormControl)
   {
      var valid : boolean = false,
          k     : any;


      for (k in boxes.value) 
      {
         var val = boxes.value[k];

         if (val) 
         {
            valid = true;
            break;
         }
      }

      if (valid) 
      {
         return null;
      }

      return {
         checkboxRequired: true
      };
   }



}

Here we've added a validateCheckboxes method which, as the name implies, handles validating the selection of checkboxes.

A FormControl is injected as a dependency for our validateCheckboxes method allowing that method to receive an abstract representation of the form input field that the FormControl is mapped to.

In the validateCheckboxes method the FormControl will relate to ALL of the checkboxes in our form, courtesy of a FormGroup object that we will subsequently be creating in the component class.

This method subsequently loops through all of the FormControls (I.e. the abstract representation of all the form checkboxes) to determine if any selections have been made by the user. If a selection has been detected the method returns null which signals that the validation has been successfully passed.

If no selection was detected the method returns a key of checkboxRequired with a value of true. This instructs the FormBuilder object (that we'll be creating in our component class shortly) that the validation has failed.

Rooting for validation

Before we can start to use the service in our application we will need to register it with the application's root module, located at src/app/app.module.ts.

This involves importing the Validator service and then adding this to the providers array 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 {}

Now we need to import and start using our service in the application's HomePage component class.

Implementing the logic

With the Validator service managing the validation for our form checkboxes we now need to plug this into our component logic using the FormBuilder API.

To do this open the src/pages/home/home.ts file and implement the following code:

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;
   public charactersCounted	    : number = 0;

   constructor(public navCtrl  : NavController,
               private _FB 	   : FormBuilder,
               private _VAL    : Validator) 
   {
       this.form 			= _FB.group({
         'message' 		    : ['', Validators.minLength(10)],
         'service' 			: _FB.group({
		    facebook       	   : [ false ],
		    flickr       	   : [ false ],
		    instagram      	   : [ false ],
		    twitter      	   : [ false ],
		    youtube      	   : [ false ],
		    whatsapp      	   : [ false ]
		 }, { validator: _VAL.validateCheckboxes })
      });
       
   }



}

We begin, as we always do, by importing the necessary modules for the component - particularly those relating to the FormBuilder API and Validator service:

import {  
	FormBuilder, 
	FormGroup,  
  	Validators 
} from '@angular/forms';
import { Validator } from '../../providers/validator';

Within the HomePage class we declare the following public properties that will be used to help manage certain aspects of the HTML form's functionality:

public form 			    : FormGroup;
public charactersCounted	: number = 0;

This is then followed by initialising our FormBuilder and Validator modules within the class constructor:

constructor(public navCtrl  : NavController,
               private _FB 	: FormBuilder,
               private _VAL : Validator) 
{
       
}

Finally we create a FormBuilder object which contains a FormControl to validate that our message input field has a minimum of 10 characters of data entered and a nested FormGroup, assigned to a key titled service, of FormControl objects for handling the validation of checkboxes in our form.

This nested FormGroup provides an initial default value of false for each FormControl that it contains. As you might already have gathered each of these FormControls maps to a specific checkbox in our form.

We then supply the validateCheckboxes method from our Validator service as the validation rule that we wish to use for managing these FormControls:

this.form 	= _FB.group({
   'message'   : ['', Validators.minLength(10)],
   'service'   : _FB.group({
      facebook     : [ false ],
      flickr       : [ false ],
      instagram    : [ false ],
      twitter      : [ false ],
      youtube      : [ false ],
      whatsapp     : [ false ]
   }, { validator: _VAL.validateCheckboxes })
});

With this in place we can now turn our attention to crafting the necessary HTML that the FormBuilder object will manage validation logic for.

Building the form

Now that our validation logic is in place we can begin constructing our form by opening the src/pages/home/home.html file and implementing the following code:

<ion-header>
  <ion-navbar>
    <ion-title>
      Validator
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>


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

      <ion-item-group margin-bottom formGroupName="service">
         <ion-item-divider color="light">
            Select your preferred social media networks
         </ion-item-divider>
		 

         <ion-item>
            <ion-label>Facebook</ion-label>
            <ion-checkbox 
               color="danger" 
               formControlName="facebook"></ion-checkbox>
         </ion-item>


         <ion-item>
            <ion-label>Flickr</ion-label>
            <ion-checkbox 
               color="danger" 
               checked="true" 
               formControlName="flickr"></ion-checkbox>
         </ion-item>


         <ion-item>
            <ion-label>Instagram</ion-label>
            <ion-checkbox 
               color="danger" 
               checked="true" 
               formControlName="instagram"></ion-checkbox>
         </ion-item>
		 

         <ion-item>
            <ion-label>Twitter</ion-label>
            <ion-checkbox 
               color="danger" 
               formControlName="twitter"></ion-checkbox>
         </ion-item>
		 

         <ion-item>
            <ion-label>YouTube</ion-label>
            <ion-checkbox 
               color="danger" 
               formControlName="youtube"></ion-checkbox>
         </ion-item>


         <ion-item>
            <ion-label>WhatsApp</ion-label>
            <ion-checkbox 
               color="danger" 
               checked="true" 
               formControlName="whatsapp"></ion-checkbox>
         </ion-item>
         
      </ion-item-group>
      

      <ion-item>
         <ion-label stacked>Message (Min 10 characters)</ion-label>
         <ion-textarea 
            maxlength="300"
            formControlName="message" 
            (keyup)="countCharacters($event)"></ion-textarea>
      </ion-item>
      

      <ion-item>
         <span class="form-help">{{ 0 + charactersCounted }}/300 Characters</span>
      </ion-item>

    
      <ion-item>
         <button
            ion-button
            color="primary"
            text-center 
            block  
            [disabled]="!form.valid">
               Submit me!
         </button>
      </ion-item>   


   </form>

</ion-content>

If you read my previous tutorial on using the FormBuilder API the above code should be fairly familiar in terms of the syntax and properties that are being used.

That said there are couple of areas that I'd like to draw attention to.

Where we have defined our <ion-item-group> tags, which are used to collect and organise the checkboxes for the social media network selections, we've also assigned a formGroupName attribute with a value of service:

<ion-item-group margin-bottom formGroupName="service">

This is the "glue" that binds the nested FormGroup of service from our component class to the form HTML so that our FormBuilder object can manage the validation of the respective checkboxes.

You'll notice that each checkbox has a formControlName attribute with a value that both reflects the name of the social media service that it is related to but also that of the FormControl in the component class that is used for validation purposes:

// FormControlNames relating to specific social media services
<ion-checkbox 
   color="danger" 
   formControlName="facebook"></ion-checkbox>

<ion-checkbox 
   color="danger" 
   formControlName="flickr"></ion-checkbox>

<ion-checkbox 
   color="danger" 
   formControlName="instagram"></ion-checkbox>

<ion-checkbox 
   color="danger" 
   formControlName="twitter"></ion-checkbox>

<ion-checkbox 
   color="danger" 
   formControlName="youtube"></ion-checkbox>

<ion-checkbox 
   color="danger" 
   formControlName="whatsapp"></ion-checkbox>


// Each of the above FormControlName values plugs into the nested FormGroup 
// of our FormBuilder object like so
'service'   : _FB.group({
   facebook     : [ false ],
   flickr       : [ false ],
   instagram    : [ false ],
   twitter      : [ false ],
   youtube      : [ false ],
   whatsapp     : [ false ]
}

Counting characters

Our form also contains the following HTML which is used to provide character counting functionality:

<ion-item>
   <ion-label stacked>Message (Min 10 characters)</ion-label>
   <ion-textarea 
      maxlength="300"
      formControlName="message" 
      (keyup)="countCharacters($event)"></ion-textarea>
</ion-item>
      

<ion-item>
   <span class="form-help">{{ 0 + charactersCounted }}/300 Characters</span>
</ion-item>

Here we simply assign a keyup event to the <ion-textarea> component which calls a countCharacters method. This is passed the $event object of the keyup event which will allow us to track when and how many characters have been entered into this field.

We then use a template expression in the following <ion-item> component to display the number of characters entered courtesy of the charactersCounted property:

<ion-item>
   <span class="form-help">{{ 0 + charactersCounted }}/300 Characters</span>
</ion-item>

There's only one snag with the above code - we haven't created the countCharacters method in our component class!

Additionally we also need to create the validateMe method for handling the data on form submission too.

Final tweaks

Let's return to the src/pages/home/home.ts file and add the following underneath the HomePage class constructor:

validateMe(val)
{
   console.log('Validating form');
   console.dir(val);
}



countCharacters(event)
{ 
   event.preventDefault();
   this.charactersCounted  = event.target.value.length;
}

The validateMe method simply outputs the value of our form to the console while the countCharacters method takes the length of data retrieved from the target element of the captured event and assigns that to the charactersCounted property.

Nothing overly complex or challenging here!

Running the app

If we now return to the Ionic CLI and, ensuring we are at the root of the form-validator project, run the ionic serve command we should see the following rendered to the browser screen:

Validator Ionic 2 application

You'll notice that the submit button is disabled until data has been entered/selected (as per our validation rules) which is a really nice UX convention and safeguards against inaccurate/incomplete data entry/selection.

Once the form has been submitted we should see output akin to the following being printed to the browser console:

Validator Ionic 2 application

In summary

As you can see implementing checkbox validation is fairly simple with Angular 2's extensible FormBuilder API.

Custom validation rules can be plugged into a FormBuilder object giving developers tremendous leverage and scope in how and what they can validate in the HTML forms for their applications.

With regards to what we've coded in the above tutorial we could potentially make the following tweaks/improvements:

  • Provide feedback in the DOM where validation may have failed
  • Rewrite the character counter as a custom component

That said I'll leave these as exercises for the reader to undertake in their own time, if they so wish.

I hope you found this tutorial useful and, as always, feel free to use the above code in your own Ionic 2 projects as you see fit and please consider using the form below to leave a comment.

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 2 for further information and examples of using forms with the Ionic 2 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