Javascript

Ionic 2 Forms – FormBuilder and Validation

I am in a hurry, so let’s do this quick. Shall we?

The Ionic 2 series is coming up nicely, and today, we’re going to discuss how to use the FormBuilder Class and Validators in our forms. Just like the previous steps in creating form Controls and ControlGroups implicitly using ngForm were straight to the point, I hope this one is as equally fun.

Update: In Ionic 2 Beta.11 using AngularJS RC4, how FormBuilder is used along with FormGroup  and other form classes change a bit. Find the update at https://blog.khophi.co/new-changes-ionic-2-forms-examples/

One of the newbie questions with Ionic 2 is how to get Ionic 2 Side menu and Tabs combined. I discuss the how-tos of that in my Ionic 2 Side Menu and Tabs article.

 

 

What we already know

It was just amazing how easy it is to create forms in Angular 2 (Ionic 2 for that matter), as we witnessed in our previous discussion. ngForms created implicitly a ControlGroup for us, which we could pass in the form input values, or the Control into the onSubmit() function we called, which posted a mere console message with the form data.

This form data, we decided to post to the console, but what could be done with it is pretty much endless. You could use the data, if from a login form, to authenticate, or create a new user profile, and many others.

All the simple and basic understanding you gained from the other article is going to help with this one too. So let’s take it slow, and get ionized!

What we want and What we’ll do

Creating a form implicitly isn’t always what you or I might want. Sometimes we want to take control of the ‘Controls’ and get down to the nitty-gritty parts. It is fun! Angular 2 doesn’t leave all the fun for itself. With FormBuilder and Validators, Angular 2 hands over the driving wheel to you. As to what you do, that’s none of its business. Who cares what you do with a Hadron Collider!

FormBuilder

The final code for our basic Ionic 2 Forms is on Github. If you missed the previous tutorial, you can check it out, it adds flesh to the code and discusses it.

Again, let’s quote the NG-Book 2 here. I can’t stress enough how good the book is. It was written for Angular 2, in Typescript, but since Ionic 2 dances with Angular 2, the concert is just as harmonious as you would expect.

Building our Controls and ControlGroups implicitly using ngForm and ngControl is convenient, but doesn’t give us a lot of customization options. A more flexible and common way to configure forms is to use a FormBuilder .
FormBuilder is an aptly-named helper class that helps us build forms. As you recall, forms are made up of Control s and ControlGroup s and the FormBuilder helps us make them (you can think of it as a “factory” object).

Think of ngForm and ngControl as autopilot and FormBuilder as manual control. We want manual control this time.

The quote from the NG-Book puts everything in perspective just the way needed to jump right into the code. So, shall we?

import { Page } from 'ionic-angular';

// see new imports we're doing, and where they're coming from?
import { FormBuilder, ControlGroup } from 'angular2/common';

@Page({
  templateUrl: 'build/pages/buildform/buildform.html',
})

export class BuildformPage {
  static get parameters() {
    // provide Angular with metadata about things it 
    // should inject in the constructor
    // http://ionicframework.com/docs/v2/getting-started/tutorial/adding-pages/
    return [[FormBuilder]];
  }

  // We inject FormBuilder by creating an argument in 
  // the constructor of our component class
  constructor(formBuilder) {
    this.nav = nav;
    this.myData = null;
    // create a new formbuilder group with 'subject' and 'message' as fields
    this.myForm = formBuilder.group({
      'subject': '',
      'message': ''
    })
  }

  // piece of cake
  onSubmit(formData) {
    console.log('Form submitted is ', formData);
    this.myData = formData;
  }
}

How gorgeous! Taking the steering manually didn’t mean we’re gonna press a million buttons and drive several pedals and gear handles to get the plane to move an inch.

It simply meant, manually creating a ControlGroup using the .group() function of the formBuilder instance auto created for us after injecting it and its related metadata. The two fields provided are essentially the form fields we’ve got or in other words, our ngControls. It doesn’t get any simpler than this, does it?

Now, what changes do we expect to see in the template? Some say I talk too much, so, please, here we go:

<ion-content padding class="getting-started">
    <p>
      Form values submitted will be displayed in console. F12 on Chrome to open console
    </p>
    <ion-list inset>
        <form [ngFormModel]="myForm" (ngSubmit)="onSubmit(myForm.value)">
            <ion-item>
                <ion-label floating>Subject</ion-label>
                <ion-input type="text" [ngFormControl]="myForm.controls['subject']"></ion-input>
            </ion-item>            
            <ion-item>
                <ion-label floating>Message</ion-label>
                <ion-textarea type="text" [ngFormControl]="myForm.controls['message']"></ion-textarea>
            </ion-item>
            <button block>
                <ion-icon name="add"></ion-icon>Add</button>
        </form>
    </ion-list>
    <!-- Angular 2 way of doing <p ngShow="myData"> bla bla </p> -->
    <p *ngIf="myData">
      You submitted: <br/>Subject: "<strong>{{ myData.subject }}</strong>" <br/> Message: "<strong>{{ myData.message }}</strong>"
    </p>
</ion-content>

You’ll have to forgive me, I need to add a word to the above, to clear what we’ve seen above. See:

<!-- Explicitly created ControlGroup. This we've deliberately created in controller,
 and using in form -->
<form [ngFormModel]="myForm" (ngSubmit)="onSubmit(myForm.value)">
<!-- Implicitly created ControlGroup. This, we're creating on the fly right in our 
template -->
<form #formData='ngForm' (ngSubmit)="onSubmit(formData.value)">

With them compared head-on, I’m sure you get the picture now. With explicitly created ControlGroup, one way to intercept and use in your template is the example you see above. In Angular 1, we all know ng-model to bind an input field value to the scope. Well, the [ngFormModel] is doing something similar. So it is binding the ControlGroup to the current form in the template. Then, it exposes a ngSubmit directive for us to enjoy for free.

This cool kid in the block makes so much sense when understood. Can seem chaotic at first, but taking a deep breath, and trying to see how the pattern works is all you need to hit the jackpot.

<ion-input type="text" [ngFormControl]="myForm.controls['subject']"></ion-input>

I doubt I need to write anything to explain what that is. In English:

I created a control group called myForm, I also created controls, two for that matter in this myForm control group. Please, find the one called ‘subject’, via key search, and bind to the input field in context.

Validation! Duh…

We’re done with using FormBuilder this quick. Did I miss anything? Hmmmmm…. Lemme see. Not that I know of. Remember, my goal is to share the easiest, simplest form of tricks in using Ionic 2 Forms.

Thus, using FormBuilder was pretty straightforward, and if you had sweat on your face at the end of that section, then it wasn’t my fault. So, Validation…

We will be simply extending the example above to include the validations. I will include the parts you’ll need to change only. So, please, open your eyes, the real ones.

import { FormBuilder, Validators, AbstractControl, ControlGroup } from 'angular2/common';

We add extra imports, namely and specifically, Validators Don’t keep wondering what magic Validators bring on board. You’ll see soon. In fact, now!

this.myForm = formBuilder.group({
  'subject': ['', Validators.required],
  'message': ['', Validators.required]
})

You see what we’re doing there? In our previous code, we had something like this, ... 'subject': '', but here, we’re adding brackets instead. Well, each Control takes accepts array too, if you wanna add multiple options.

NOTE: If you played with the formBuilder controls key-value in the previous example, the value part, you would have noticed that, what you put in the 'subject': 'input value' – the string ‘input value’ will be prepopulated in your templates when reloaded. With this approach, you could prepopulated a form with data from say, the server by passing them into whatever form control as the first item.

Quoting ng-book:

The most flexible way to deal with individual Controls in your view is to set each Control up as an instance variable in your Component [or Page] definition class.

To that effect, this is how we would do it:

this.subject = this.myForm.controls['subject'];
this.message = this.myForm.controls['message']

The above gives us both controls in our template to dance, sing and play with. It is that easy, and manipulating in the template will happen like this:

<ion-content padding class="getting-started">
    <p>
        Form values submitted will be displayed in console. F12 on Chrome to open console
    </p>
    <ion-list inset>
        <form [ngFormModel]="myForm" (ngSubmit)="onSubmit(myForm.value)">
            <ion-item>
                <ion-label floating>Subject</ion-label>
                <ion-input type="text" [ngFormControl]="myForm.controls['subject']"></ion-input>
            </ion-item>
            <p *ngIf="!subject.valid && subject.touched" danger>Subject is empty</p>
            <p *ngIf="subject.hasError('required') && subject.touched" danger>Subject is required</p>
            <ion-item>
                <ion-label floating>Message</ion-label>
                <ion-textarea type="text" [ngFormControl]="myForm.controls['message']"></ion-textarea>
            </ion-item>
            <p *ngIf="!message.valid && message.touched" danger>Message is empty</p>
            <button block>
                <ion-icon name="add"></ion-icon>Add</button>
        </form>
    </ion-list>
    <!-- Angular 2 way of doing <p ngShow="myData"> bla bla </p> -->
    <p *ngIf="myData">
        You submitted:
        <br/>Subject: "<strong>{{ myData.subject }}</strong>"
        <br/> Message: "<strong>{{ myData.message }}</strong>"
    </p>
</ion-content>

That’s a lot to look at, so let’s break things down a bit.

<p *ngIf="!subject.valid && subject.touched" danger>Subject is empty</p>
<p *ngIf="subject.hasError('required') && subject.touched" danger>Subject is required</p>

That’s where the focus is. Before you get confused, remember we added both form controls, ‘subject’ and ‘message’ to an instance variable called subject and message in our controller? They are the one’s you’re seeing used in action here, in the form of !subject.valid etc.

 

[wp_ad_camp_1]

 

So, in English, taking the first line first:

If my form control, the explicitly created one passed from the controller, which is same as if we used ngControl, if that control isn’t valid and has been touched, display, “Subject is empty”

Since we are using an && (AND) operator here, we expect both conditions to be true for the element to be shown. When the page is loaded the first time, subject.touched is false. We haven’t interacted with that input field. In addition, !subject.valid evaluates to false again, because, well, our form input “requires” something to be put in. So, we have two false statements. Our error message doesn’t get to show. Win!

Note: Browser standard validators like:

  • Required
  • minLength
  • maxLength

are allowed on an ionic field. We’re even using the required

However, try leaving the input field focus with nothing in the field, then you’ll be shown the error message because 1) You’ve touched the field, and 2) it is empty, although required. Am I right? I might be confused at this point, but I am sure you get the point.

The throwing in of the subject.touched explains another type of way to validate your form, via interactions. So if the field has been touched, say something, if not, who cares, the user isn’t probably ready.

The above example covers only a tiny part of what validation combos could be achieved. For example, you could decide to disable the add button until everything in the form checks out. So  something like this on the ion button

<button block [disabled]="!subject.valid || !message.valid">
                <ion-icon name="add"></ion-icon>Add</button>

… which is simply saying, at least, one of the input fields should be unblemished. Pure win, for everyone involved. Remember, though, such hiding of the button does not necessarily mean what the user sends is what you wanted. Your validation might check for allowing only alphanumeric input value. It is important to check everything again on the server before saving to DB unless you don’t care what comes in.

In an app, it might be hard or perhaps uncommon for someone unhiding a button so as to go ahead and send something, but if building apps with AngularJS that’ll run in the browser, remember a user could just open the code inspector and tweak things to suit their needs to enable the button.

At this point, under the validations, this is what we can end up with:

<ion-navbar>
    <button menuToggle>
        <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>Form with Validation</ion-title>
</ion-navbar>
<ion-content padding class="getting-started">
    <p>
        Form values submitted will be displayed in console. F12 on Chrome to open console
    </p>
    <ion-list inset>
        <form [ngFormModel]="myForm" (ngSubmit)="onSubmit(myForm.value)">
            <ion-item>
                <ion-label floating>Subject</ion-label>
                <ion-input type="text" [ngFormControl]="myForm.controls['subject']" required></ion-input>
            </ion-item>
            <p *ngIf="!subject.valid && subject.touched" danger>Subject is empty</p>
            <p *ngIf="subject.hasError('required') && subject.touched" danger>Subject is required</p>
            <ion-item>
                <ion-label floating>Message</ion-label>
                <ion-textarea type="text" [ngFormControl]="myForm.controls['message']"></ion-textarea>
            </ion-item>
            <p *ngIf="!message.valid && message.touched" danger required>Message is empty</p>
            <button block [disabled]="!subject.valid || !message.valid">
                <ion-icon name="add"></ion-icon>Add</button>
        </form>
    </ion-list>
    <!-- Angular 2 way of doing <p ngShow="myData"> bla bla </p> -->
    <p *ngIf="myData">
        You submitted:
        <br/>Subject: "<strong>{{ myData.subject }}</strong>"
        <br/> Message: "<strong>{{ myData.message }}</strong>"
    </p>
</ion-content>

Then formvalidate.js? Here:

import {Page, NavController} from 'ionic-angular';
import { FormBuilder, Validators, AbstractControl, ControlGroup } from 'angular2/common';

@Page({
  templateUrl: 'build/pages/formvalidate/formvalidate.html',
})
export class FormvalidatePage {
  static get parameters() {
    return [[FormBuilder]];
  }

  constructor(formBuilder) {
    this.nav = nav;
    this.myData = null;
    this.myForm = formBuilder.group({
      'subject': ['', Validators.required],
      'message': ['', Validators.required]
    })

    this.subject = this.myForm.controls['subject'];
    this.message = this.myForm.controls['message']
  }

  onSubmit(formData) {
    console.log('Form submitted is ', formData);
    this.myData = formData;
  }
}

Well, that was just by the way, but if there are points you take home from this article, they likely might be:

  • Explicitly building a ControlGroup and Controls using FormBuilder is straightforward and easy too.
  • The power of validation is always in your hands. Dance with it however you like.

Thanks for your time, and make sure to check out the source code for the entire examples above on Github. Please, feel free to draw my attention to any mistakes herein. Still on the learning curve with Ionic 2, and will appreciate expert tips and guidance, if and when I’m going wrong.

 

[wp_ad_cam_3]

Related Articles

Back to top button