Creating a complex form with input validation

In this section, you will build an app to demonstrate form validation using ngForm and ngControl. Here is a screenshot of the form:

Creating a complex form with input validation

If the user tries to submit without providing valid information, the form will show the following error:

Creating a complex form with input validation

Basically, the Name field is required. The Phone field is the number type, but is optional. The Comment field is required and the user must enter at least four characters. Of course, this is just for demonstration of the input length. The user, finally, must agree to the terms and conditions via the toggle input.

After a successful validation, the user will be taken to the second screen with a summary of the previous screen, as illustrated in the following screenshot:

Creating a complex form with input validation

Getting ready

This app example could work either in a browser or on a physical device. However, you can optionally connect your physical device to verify the Phone field for number keypad.

How to do it…

Observe the following the instructions:

  1. Create a new MyFormValidation app using the blank template, as shown, and go to the MyFormValidation folder:
      $ ionic start MyFormValidation blank --v2
      $ cd MyFormValidation
    
  2. Open the ./src/app/app.module.ts file and replace the content with the following code:
    import { NgModule } from '@angular/core';
    import { IonicApp, IonicModule } from 'ionic-angular';
    import { MyApp } from './app.component';
    import { HomePage } from '../pages/home/home';
    import { ThankyouPage } from '../pages/thankyou/thankyou';
    import { MyFormService } from '../services/myform';
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage,
        ThankyouPage
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage,
        ThankyouPage
      ],
      providers: [MyFormService]
    })
    export class AppModule {}

    You may realize that there is a common service to be used across the app, called MyFormService here. This example also has a second page, called ThankyouPage.

  3. Now, let's create the service by first creating a directory, as shown:
      $ mkdir ./src/services
    
  4. Create the myform.js file in the component's directory that you just created, as follows:
    import {Injectable} from '@angular/core';
    
    @Injectable()
    export class MyFormService {
      public name: string = '';
      public phone: number;
      public comment: string = '';
      
      constructor() {
      }
    }

    This example will keep the service component simple for demonstration purposes.

  5. Open and edit the ./src/pages/home/home.html template, as shown:
    <ion-header>
      <ion-navbar color="primary">
        <ion-title>
          Contact Form
        </ion-title>
      </ion-navbar>
    </ion-header>
    
    <ion-content>
      <p class="center">
        <ion-icon class="large lighter" primary name="contact"></ion-icon>
      </p>
      <form #f="ngForm" novalidate (ngSubmit)="onSubmit(f)">
        <ion-list>
    
          <ion-item>
            <ion-label floating>Name</ion-label>
            <ion-input type="text" name="name" required [(ngModel)]="data.name"></ion-input>
          </ion-item>
          <div [hidden]="f.controls.name && (f.controls.name.valid || (f.controls.name.pristine && !isSubmitted))" class="note danger">
            Name is required
          </div>
    
          <ion-item>
            <ion-label floating>Phone</ion-label>
            <ion-input type="tel" name="phone" [(ngModel)]="data.phone"></ion-input>
          </ion-item>  
          
          <ion-item>
            <ion-label floating>Comment</ion-label>
            <ion-input type="text" required minlength=4 name="comment" [(ngModel)]="data.comment"></ion-input>
          </ion-item>
          <div *ngIf="(isSubmitted && f.controls.comment && f.controls.comment.pristine) || ((f.controls.comment) && (f.controls.comment.dirty && f.controls.comment.errors))" class="note danger">
            Please enter {{ 4 - (f.controls.comment.errors.minlength ? f.controls.comment.errors.minlength.actualLength : 0) }} more characters
          </div>
                
          <ion-item class="tos">
            <ion-toggle item-left [(ngModel)]="data.tos" name="tos" type="button" (click)="noSubmit($event)"></ion-toggle>
            <ion-label item-right>Agree to terms and conditions</ion-label>
          </ion-item>
          
          <div [hidden]="(!isSubmitted) || (f.controls.tos && data.tos)" class="note danger">
            Please check Agree!
          </div>
    
        </ion-list>
        
        <div class="center">
          <button ion-button type="submit" round outline>Submit</button>
        </div>
      </form>
    
    </ion-content>

    This is probably the most complicated part of the form validation process because there are many places where you have to embed validation logic for the input.

  6. Open and replace the content of the ./src/pages/home/home.scss file with the following code:
    .center {
      text-align: center;
    }
    
    ion-icon.large {
      font-size: 7em;
    }
    
    ion-icon.lighter {
      opacity: 0.5;
    }
    
    ion-list > .item:first-child {
      border-top: 0;
    }
    
    ion-list > .item:last-child, ion-list > ion-item-sliding:last-child .item {
      border-bottom: 0;
    }
    
    .tos {
      padding-top: 10px;
      
      ion-toggle {
        padding-left: 0px;  
      }
      .item-inner {
        border-bottom: 0;
      }
    }
    
    .item ion-toggle {
      padding-left: 0;
    }
    
    .note.danger {
      padding-left: 16px;
      color: #d14;
    }
  7. Open ./src/pages/home/home.ts for editing with the following code:
    import { Component } from '@angular/core';
    import { NavController } from 'ionic-angular';
    import { ThankyouPage } from '../thankyou/thankyou';
    import { MyFormService } from '../../services/myform';
    
    @Component({
      selector: 'page-home',
      templateUrl: 'home.html'
    })
    export class HomePage {
      private data: any;
      private isSubmitted: Boolean = false;
      
      constructor(public nav: NavController, private formData: MyFormService) {
        this.nav = nav;
        this.formData = formData;
        this.data = {
          name: '',
          phone: '',
          comment: '',
          tos: false
        }
      }
      
      onSubmit(myForm) {
        this.isSubmitted = true;
        console.log('onSubmit');
        console.log(myForm);
        
        if ((myForm.valid) && (myForm.value.tos)) {
          this.formData.name = this.data.name;
          this.formData.phone = this.data.phone;
          this.formData.comment = this.data.comment; 
          this.nav.push(ThankyouPage);
        }
      }
      
      noSubmit(e) {
        e.preventDefault();
      }
    }

    You may note that there isn't much validation code in the JavaScript part. This means that the template takes care of a lot of the validations. There is also an import command for a thankyou page, which you will have to create next.

  8. Now, let's create the thankyou folder, as follows:
      $ mkdir ./src/pages/thankyou
    
  9. Create a thankyou.js file in the component's directory that you just created, as shown:
    import { Component } from '@angular/core';
    import { MyFormService } from '../../services/myform'
    
    @Component({
      templateUrl: 'thankyou.html'
    })
    export class ThankyouPage {
      
      constructor(private formData: MyFormService) {
        this.formData = formData;
      }
    
    }

    This page just renders the data from the MyFormService service. So, you can keep it very simple.

  10. Create thankyou.html in the ./src/pages/thankyou, folder, as illustrated:
    <ion-header>
      <ion-navbar color="secondary">
        <ion-title>
          Thank You
        </ion-title>
      </ion-navbar>
    </ion-header>
    
    <ion-content>
      <h6 class="padding">
        You submitted the following information
      </h6>
      
      <div class="my-table">
        <ion-row>
          <ion-col width-25 class="my-label">Name</ion-col>
          <ion-col width-75>{{ formData.name }}</ion-col>
        </ion-row>
        <ion-row>
          <ion-col width-25 class="my-label">Phone</ion-col>
          <ion-col width-75>{{ formData.phone }}</ion-col>
        </ion-row>
        <ion-row>
          <ion-col width-25 class="my-label">Comment</ion-col>
          <ion-col width-75>{{ formData.comment }}</ion-col>
        </ion-row>
      </div>  
    </ion-content>
  11. Create thankyou.scss in the ./src/pages/thankyou folder, as shown:
    h6.padding {
      color: #4C555A;
      padding: 10px;
    }
    
    .my-label {
      text-align: right;
      font-weight: bold;
    }
    
    .my-table {
      ion-row {
        color: #4C555A;
        padding: 0;
        height: 30px;
      }
      
      ion-row + ion-row {
        margin-top: 0;
      }
      
      ion-row:nth-child(odd) ion-col {
        background: #F9FAFB;
      }
    }
  12. Edit the ./app/app.scss file to ensure that you include both the .scss files in the two pages, as follows:
    @import '../pages/home/home';
    @import '../pages/thankyou/thankyou';
  13. Go to your Terminal and run the app with the following command:
      $ ionic serve
    

How it works…

Let's start with the home.html file, where most of the validation code is located. If you look at the structure of this page, it's very typical. You have <ion-navbar> with <ion-title>. The <form> element must be inside the <ion-content> area.

Tip

It's a requirement to use the <form> element for Angular 2 validation to work. Otherwise, there will be no submit event and you cannot catch errors for each input.

form has the following attributes:

<form #f="ngForm" novalidate (ngSubmit)="onSubmit(f)">

To assign a local variable on the fly, you use the # sign. This means that you want the f variable to refer to ngForm, which is automatically created from Angular 2. This is a special object that contains everything related to the current form. You are advised to use novalidate to bypass the default HTML5 validation because you are using Angular 2 for validation instead. Otherwise, the form will acquire conflicts. The (ngSubmit) is pretty much an event to trigger the onSubmit(f) function whenever the button with type=submit is touched or clicked. When you submit the form, it will pass the f variable along so that you can process the object inside the onSubmit method.

The form template consists of just <ion-list> and <ion-item>. You just need to know how to validate each input and display the error. Let's use the Name field as the first example. This is the <ion-input> for Name:

<ion-input type="text" name="name" required [(ngModel)]="data.name"></ion-input>

The following is the error displayed:

<div [hidden]="f.controls.name && (f.controls.name.valid || (f.controls.name.pristine && !isSubmitted))" class="note danger">
  Name is required
</div>

To validate, you must assign name a local variable name. This is to refer to that input using f.controls.name in other areas. Recall that the f variable has been declared previously as the ngForm object. Here is a view of how the ngForm is structured:

How it works…

You can view this using the Chrome Developer console because the code actually gives this output when you submit the form.

The error message Name is required will be hidden when either of the following conditions takes place:

  • The form has not been submitted yet. Otherwise, people will see the error message right away before they even type in something. This is not a good user experience. To check for this, you have to use a temporary Boolean, called isSubmitted. The f.controls.name.pristine variable means that the input has not been modified. The opposite of this would be f.controls.name.dirty.
  • The f.controls.name.valid variable is true. However, you cannot check this right away because, if the input is empty, the name object does not exist yet. That's why you need to check for the existence of f.controls.name before checking for the valid Boolean.

There is no need to check the phone requirement; so, you just need to assign name and a model, as shown:

<ion-input type="tel" name="phone" [(ngModel)]="data.phone"></ion-input>

For the Comment field, there is a need to validate using both required and minlength=4, as follows:

<ion-input type="text" required minlength=4 name="comment" [(ngModel)]="data.comment"></ion-input>

You may think required is unnecessary because, if the length is zero, Angular 2 will trigger an error flag. However, that is not true. When the user doesn't enter anything in the input, the input will have no length because the variable doesn't exist. That's why you need to check for both scenarios.

The error message for the Comment field is quite interesting because it shows the number of characters the user needs to enter, as shown in the following code:

<div *ngIf="(isSubmitted && f.controls.comment && f.controls.comment.pristine) || ((f.controls.comment) && (f.controls.comment.dirty && f.controls.comment.errors))" class="note danger">
  Please enter {{ 4 - (f.controls.comment.errors.minlength ? f.controls.comment.errors.minlength.actualLength : 0) }} more characters
</div>

The main idea here is that you only want to show this div when the form is submitted and it's pristine via f.controls.comment.pristine. This means that the user has not entered anything in the form. You also want to show the message when the form is dirty and has errors via f.controls.comment.errors. If you inspect the console, you can see a list of many detailed errors under the f.controls.comment.errors object. In order to tell the user how many characters they have left to enter, you have to first check f.controls.comment.errors.minlength because, if that variable doesn't exist, there is no error or the comment input is empty. If you do not check for this, you will get a parse error later on.

In your home.ts file, the onSubmit method must toggle the isSubmitted Boolean to true, as shown in the following code snippet:

onSubmit(myForm) {
  this.isSubmitted = true;
  console.log('onSubmit');
  console.log(myForm);
  
  if ((myForm.valid) && (myForm.value.tos)) {
    this.formData.name = this.data.name;
    this.formData.phone = this.data.phone;
    this.formData.comment = this.data.comment;
    this.nav.push(ThankyouPage);
  }
}

Then, you have to do a general check for myForm.valid and myForm.value.tos. You may wonder why we are checking for tos here instead of validating it inside the template. The reason is that there is no way to validate a toggle button in Angular 2 since it doesn't make sense to do so as it cannot be required. Therefore, you have to do a custom validation here to make sure it's true in the form. This means that the user has checked the Agree to terms and conditions toggle.

This is a minor detail that could be a bug in Ionic 2 (currently Beta 21):

noSubmit(e) {
  e.preventDefault();
}

For each toggle button, it acts as a type=submit button by default since there is no type attribute assigned. That's why you need to cancel the submit event by calling preventDefault().

Tip

Refer to the W3 website, at https://www.w3.org/TR/html-markup/button.html, for information about the default behavior of the button element.

The thankyou page is very self-explanatory because you just parse the formData object in the template by getting the data from the MyFormService service.

See also

Check out the following links for more information:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.64.89