Advanced Forms in Ionic

Advanced Forms & Validation in Ionic 2 & 3



·

You may be familiar with using [(ngModel)] to set up two way data binding between inputs in your templates, and variables in your class definition. For example you might have an input field like this:

<ion-input type="text" [(ngModel)]="myInput"></ion-input>

and then in your class definition you could access that value through:

this.myInput

In this tutorial we are going to look at a more complex, but also more powerful, way to set up forms in an Ionic 2 application. We will be building a multi stage sign up form as an example. Here’s what it will look like:

Form Validation in Ionic 2

The method we will use makes it easier to manage forms with many fields, and it also allows us to more easily validate data that is entered, this includes:

  • Basic validation using the built in Angular 2 Validators
  • Custom validation using our own custom validators
  • Multiple validations for a single input field
  • Asynchronous validation (e.g. checking if a username is already taken)

We will be using FormBuilder to create our forms and Validators, which are used in conjunction with FormBuilder, to validate the input.

IMPORTANT: Validating input with Javascript is only for user experience. It can easily be circumvented, so if you are sending this data off to a server you must also validate and sanitize the input on the server side.

An Introduction to Forms in Angular 2 and Ionic 2

When building forms in Angular 2 it is important to understand what a FormControl is and what a FormGroup is.

A FormControl is tied to an input field, it has a value (i.e. the value the user has entered), and a validation state (i.e. whether or not the value is valid based on an optional validation function). A FormGroup is a collection of FormControls.

A single FormControl can be manually created using the following syntax:

this.myControl = new FormControl('value', *validation function goes here*, *asynchronous validation function goes here*);

and this is tied to the input in the template by using the ngControl directive:

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

We can also create and manage multiple FormControls at once in a single FormGroup, like this:

this.myForm = new FormGroup({
    firstName: new FormControl('Josh'),
    lastName: new FormControl('Morony')
});

We can use another service called FormBuilder to make the process of creating a FormGroup a little easier, and it would look like this:

this.myForm = formBuilder.group({
    firstName: ['value'],
    lastName: ['value', *validation function goes here*],
    age: ['value', *validation function goes here*, *asynchronous validation function goes here*]
});

We use the formBuilder.group function to create our FormGroup by supplying an object containing each of our FormControls. We must also set the formGroup property on the parent <form> to have the same name as our FormBuilder group, which would look like this:

    <form [formGroup]="myForm">
        <ion-input formControlName="firstName" type="text"></ion-input>
        <ion-input formControlName="lastName" type="text"></ion-input>
        <ion-input formControlName="age" type="number"></ion-input>
    </form>

For each FormControl we create, we also supply an array of values.

The first value is required (although it can just be an empty string) and is the default value of the FormControl. The second value is optional, and is a validation function that is used to check the value of the FormControl. The third value is also optional, and is basically the same as the second except that it is for asynchronous validation. This means if you need to perform a check that is not instant (like checking if a username already exists on a server) then you can use an asynchronous validation function. As you’ll notice in the example above, we have not always included both the validation functions since they are optional.

When supplying Validators we can use ones that are provided by default in Angular 2 from @angular/forms, or we can create our own custom validators. The default Validators include:

  • Validators.required
  • Validators.minLength(number)
  • Validators.maxLength(number)
  • Validators.pattern(‘pattern’)

Most of these are pretty obvious. The required validator will ensure that a value is entered, minLength and maxLength ensure that the length of the input is of a certain length, and pattern uses a regex pattern to validate the input.

So an actual FormGroup created by FormBuilder might look like this:

this.slideOneForm = formBuilder.group({
    firstName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])],
    lastName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])],
    age: ['', AgeValidator.isValid]
});

For the first two controls we are supplying three different validators. We want the input to be shorter than 30 characters, only contain letters and spaces, and it is also a required field. To use multiple Validators we can use the Validators.compose function, and supply it an array of the Validators we want to use.

The last control uses a custom validator to validate the age control (we will be covering how to do that later). This should give you some background, and we are about to go through a practical example in Ionic 2 now, but if you would like some more background information on forms in Angular 2, I highly recommend these two posts:

Now let’s jump into building a real life example: a multi screen sign up form with validation.

Before we Get Started

Last updated for Ionic 3.0.0

Before you go through this tutorial, you should have at least a basic understanding of Ionic 2 concepts. You must also already have Ionic 2 set up on your machine.

If you’re not familiar with Ionic 2 already, I’d recommend reading my Ionic 2 Beginners Guide first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic 2, then take a look at Building Mobile Apps with Ionic 2.

Generate a New Ionic 2 Application

We’ll start off by generating a new blank Ionic 2 application.

Generate a new application by running the following command:

ionic start advanced-forms blank --v2

Set up the Slides

To set up our multi page form we are going to use the Slides component. We will set up two slides, and add a method to go backwards and forwards between the slides (as well as having the ability to just swipe between the two).

Modify src/pages/home/home.html to reflect the following:

<ion-header>
    <ion-navbar color="primary">
      <ion-title>
        Sign Up
      </ion-title>
      <ion-buttons start>
        <button ion-button icon-left (click)="prev()"><ion-icon name="arrow-back"></ion-icon> Prev</button>
      </ion-buttons>
      <ion-buttons end>
        <button ion-button icon-right (click)="next()">Next <ion-icon name="arrow-forward"></ion-icon></button>
      </ion-buttons>
    </ion-navbar>
</ion-header>

<ion-content>

    <ion-slides #signupSlider>

      <ion-slide>

      </ion-slide>

      <ion-slide>

      </ion-slide>

    </ion-slides>

</ion-content>

We’ve set up out two slides here, and we’ve also created a local variable #signupSlider so that we can grab a reference to the slider and control if from our class definition. Also notice that we’ve set up Next and Prev buttons in the navbar, we will use those to trigger the next and previous slides.

Modify src/pages/home/home.ts to reflect the following:

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

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

    @ViewChild('signupSlider') signupSlider: any;

    slideOneForm: FormGroup;
    slideTwoForm: FormGroup;

    submitAttempt: boolean = false;

    constructor(public navCtrl: NavController, public formBuilder: FormBuilder) {

    }

    next(){
        this.signupSlider.slideNext();
    }

    prev(){
        this.signupSlider.slidePrev();
    }

    save(){

    }

}

There’s quite a few things we’re importing, and some things we’re setting up for later, so let’s talk through that.

We’re importing and using ViewChild to grab a reference to our signupSlider which we then assign to the member variable signupSlider. We are importing FormBuilder, FormGroup, and Validators, all of which we have just been discussing, and we set up member variables for the two forms we will be creating: slideOneForm and slideTwoForm.

We’ve also set up the next() and prev() functions, as well as an empty function save() which we will make use of later.

Set up the Forms

Now that we’ve got the basic structure set up, let’s build our forms.

Modify the first slide in src/pages/home/home.html to contain the following:

        <p *ngIf="submitAttempt" style="color: #ea6153;">Please fill out all details accurately.</p>

        <ion-list no-lines>

            <form [formGroup]="slideOneForm">

                <ion-item>
                    <ion-label floating>First Name</ion-label>
                    <ion-input formControlName="firstName" type="text"></ion-input>
                </ion-item>

                <ion-item>
                    <ion-label floating>Last Name</ion-label>
                    <ion-input formControlName="lastName" type="text"></ion-input>
                </ion-item>

                <ion-item>
                    <ion-label floating>Age</ion-label>
                    <ion-input formControlName="age" type="number"></ion-input>
                </ion-item>

            </form>

        </ion-list>

Here we’ve set up our form with a formGroup property with a value of slideOneForm which is the name of the member variable we set up in our class, which we will use to create a FormGroup with FormBuilder in just a moment. We’ve then set up all of our inputs and given them each a formControlName which we will also hook into shortly with the FormBuilder.

Now let’s set up our form with FormBuilder in the class definition.

Modify the constructor in home.ts to reflect the following:

    constructor(public navCtrl: NavController, public formBuilder: FormBuilder) {

        this.slideOneForm = formBuilder.group({
            firstName: [''],
            lastName: [''],
            age: ['']
        });

    }

Here we’re setting up our FormGroup with the three Controls we have, firstName, lastName, and age. We’ve just initialised them with an empty default value, and no validators for now. Now let’s do the same for our other form.

Modify the second slide in src/pages/home/home.html to contain the following:

<br />        <ion-list no-lines>

            <form [formGroup]="slideTwoForm">

                <ion-item>
                    <ion-label floating>Username</ion-label>
                    <ion-input formControlName="username" type="text"></ion-input>
                </ion-item>

                <ion-item>
                    <ion-label floating>Privacy</ion-label>
                    <ion-select formControlName="privacy">
                        <ion-option value="public" checked="true">Public</ion-option>
                        <ion-option value="friends">Friends Only</ion-option>
                        <ion-option value="private">Private</ion-option>
                    </ion-select>
                </ion-item>

                <ion-item>
                    <ion-label floating>Bio</ion-label>
                    <ion-textarea formControlName="bio"></ion-textarea>
                </ion-item>

            </form>

        </ion-list>

        <button ion-button full color="primary" (click)="save()">Create Account!</button>

We’ve basically done the exact same thing here. We’re using a <ion-select> input here so the syntax is a little different, and we won’t be setting up any validation on the bio field so we don’t need to call the elementChanged function. We’ve also added a “Create Account!” button that will trigger the save() function. Apart from that, everything else is the same.

Modify the constructor in src/pages/home/home.ts to reflect the following:

    constructor(public navCtrl: NavController, public formBuilder: FormBuilder) {

        this.slideOneForm = formBuilder.group({
            firstName: [''],
            lastName: [''],
            age: ['']
        });

        this.slideTwoForm = formBuilder.group({
            username: [''],
            privacy: [''],
            bio: ['']
        });

    }

Now both our forms are set up, but they do not currently have any validation. So let’s move on to that.

Set up Validation

We’re going to have a mixed bag of validations going on here so we can see a bunch of different methods for validation. Here’s a rundown of what we want to do:

  • All fields except for bio should be required
  • firstName should only contain letters and spaces, and be less than 30 characters
  • firstName should only contain letters and spaces, and be less than 30 characters
  • age must be older than 18, and also realistic (i.e. not extremely old, a whole number)
  • username should only contain letters, and it should also check to see if the username is already taken asynchronously
  • privacy is simply required
  • bio is optional and has no validation

Some of the validations we want to do are covered by Angular 2 by default, so we will focus on those first.

Modify the constructor in home.ts to reflect the following:

    constructor(public navCtrl: NavController, public formBuilder: FormBuilder) {

        this.slideOneForm = formBuilder.group({
            firstName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])],
            lastName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])],
            age: ['']
        });

        this.slideTwoForm = formBuilder.group({
            username: ['', Validators.compose([Validators.required, Validators.pattern('[a-zA-Z]*')])],
            privacy: ['', Validators.required],
            bio: ['']
        });

    }

Now we’ve added the appropriate validators, using the default validators provided by Angular 2, to set up validation for all of the fields except age and username. We will be creating our own custom validators for those now.

Create a new file and folder at src/validators/age.ts and add the following:

import { FormControl } from '@angular/forms';

export class AgeValidator {

    static isValid(control: FormControl): any {

        if(isNaN(control.value)){
            return {
                "not a number": true
            };
        }

        if(control.value % 1 !== 0){
            return {
                "not a whole number": true
            };
        }

        if(control.value < 18){
            return {
                "too young": true
            };
        }

        if (control.value > 120){
            return {
                "not realistic": true
            };
        }

        return null;
    }

}

This is the simpler of the two validators. We simply pass in the current value through a reference to the FormControl. We then test the value against certain conditions. If any of these conditions are met then we return an object describing the error, returning a value means that the validation has failed. If none of the conditions are met then the validation has succeeded so we simply return null.

Create a new file at app/validators/username.ts and add the following:

import { FormControl } from '@angular/forms';

export class UsernameValidator {

  static checkUsername(control: FormControl): any {

    return new Promise(resolve => {

      //Fake a slow response from server

      setTimeout(() => {
        if(control.value.toLowerCase() === "greg"){

          resolve({
            "username taken": true
          });

        } else {
          resolve(null);
        }
      }, 2000);

    });
  }

}

Rather than actually checking a server to see if a username exists, we use a setTimeout to fake a slow response from a server. It’s the same idea here in that we return null if the validation succeeds, but instead of simply returning the value we return a Promise because this is an asynchronous validator.

Now we need to import those two validators.

Add the following two imports to the top of src/pages/home/home.ts:

import { AgeValidator } from  '../../validators/age';
import { UsernameValidator } from  '../../validators/username';

Now our validators are available to use, we just need to add them to our constructor.

Modify the constructor in src/pages/home/home.ts to reflect the following:

    constructor(public navCtrl: NavController, public formBuilder: FormBuilder) {

        this.slideOneForm = formBuilder.group({
            firstName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])],
            lastName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])],
            age: ['', AgeValidator.isValid]
        });

        this.slideTwoForm = formBuilder.group({
            username: ['', Validators.compose([Validators.required, Validators.pattern('[a-zA-Z]*')]), UsernameValidator.checkUsername],
            privacy: ['', Validators.required],
            bio: ['']
        });

    }

In the case of age we just add the custom validator as we did the default ones. With username however, take note that we don’t just add the validator to Validators.compose as the second value, we provide it as the third value in the array – this is because it is an asynchronous validator (and a FormControl can have both synchronous and asynchronous validators).

We’re going to make one more change to the class definition, and that’s to finish off the save() function.

Modify the save() function in src/pages/home/home.ts to reflect the following:

    save(){

        this.submitAttempt = true;

        if(!this.slideOneForm.valid){
            this.signupSlider.slideTo(0);
        } 
        else if(!this.slideTwoForm.valid){
            this.signupSlider.slideTo(1);
        }
        else {
            console.log("success!")
            console.log(this.slideOneForm.value);
            console.log(this.slideTwoForm.value);
        }

    }

First we set submitAttempt to true to indicate that the user has attempted to submit the form (remember, we will be using this to decide whether or not to style fields with the invalid styling). Then we check if both of the forms are valid, if they aren’t then we swap to the slide with the errors.

If both of the forms are valid then we hit our success condition, and simply log out the form values (for you to do with as you please now).

Styling the Form

Our validators are completely functional now, but what’s the use of this fancy validation if we can’t indicate what’s going on to the user? We will be modifying our template now to make use of these validators. First we will create some styles to use.

Modify src/pages/home/home.scss to reflect the following:

.ios, .md {

    page-home {

        p {
            font-size: 0.8em;
            color: #d2d2d2;
        }

        .swiper-slide {
            display: block;
        }

        ion-label, .select-text {
            margin-left: 10px;
        }

        ion-input, ion-select, ion-textarea {
            background-color: #f2f2f2;
            padding: 5px 10px;
        }

        .invalid {
            border: 1px solid #ea6153;
        }

    }

}

We’ve added a few styles here, but most notably we’ve created an invalid class, which will give the input a red border.

Modify the slides in src/pages/home/home.html to reflect the following:

    <ion-slides #signupSlider pager>

      <ion-slide>

        <p *ngIf="submitAttempt" style="color: #ea6153;">Please fill out all details accurately.</p>

        <ion-list no-lines>

            <form [formGroup]="slideOneForm">

                <ion-item>
                    <ion-label floating>First Name</ion-label>
                    <ion-input formControlName="firstName" type="text" [class.invalid]="!slideOneForm.controls.firstName.valid && (slideOneForm.controls.firstName.dirty || submitAttempt)"></ion-input>
                </ion-item>

                <ion-item *ngIf="!slideOneForm.controls.firstName.valid  && (slideOneForm.controls.firstName.dirty || submitAttempt)">
                    <p>Please enter a valid name.</p>
                </ion-item>

                <ion-item>
                    <ion-label floating>Last Name</ion-label>
                    <ion-input formControlName="lastName" type="text" [class.invalid]="!slideOneForm.controls.lastName.valid && (slideOneForm.controls.age.dirty || submitAttempt)"></ion-input>
                </ion-item>

                <ion-item *ngIf="!slideOneForm.controls.lastName.valid  && (slideOneForm.controls.lastName.dirty || submitAttempt)">
                    <p>Please enter a valid name.</p>
                </ion-item>

                <ion-item>
                    <ion-label floating>Age</ion-label>
                    <ion-input formControlName="age" type="number" [class.invalid]="!slideOneForm.controls.age.valid && (slideOneForm.controls.age.dirty || submitAttempt)"></ion-input>
                </ion-item>

                <ion-item *ngIf="!slideOneForm.controls.age.valid  && (slideOneForm.controls.age.dirty || submitAttempt)">
                    <p>Please enter a valid age.</p>
                </ion-item>

            </form>

        </ion-list>

      </ion-slide>

      <ion-slide>

        <ion-list no-lines>

            <form [formGroup]="slideTwoForm">

                <ion-item>
                    <ion-label floating>Username</ion-label>
                    <ion-input [class.invalid]="!slideTwoForm.controls.username.valid && (slideTwoForm.controls.username.dirty || submitAttempt)" formControlName="username" type="text"></ion-input>
                </ion-item>

                <ion-item *ngIf="slideTwoForm.controls.username.pending">
                    <p>Checking username...</p>
                </ion-item>

                <ion-item *ngIf="!slideTwoForm.controls.username.valid && !slideTwoForm.controls.username.pending && (slideTwoForm.controls.username.dirty || submitAttempt)">
                    <p>Sorry, that username can not be used!</p>
                </ion-item>

                <ion-item>
                    <ion-label floating>Privacy</ion-label>
                    <ion-select [class.invalid]="!slideTwoForm.controls.privacy.valid && (slideTwoForm.controls.privacy.dirty || submitAttempt)" formControlName="privacy">
                        <ion-option value="public" checked="true">Public</ion-option>
                        <ion-option value="friends">Friends Only</ion-option>
                        <ion-option value="private">Private</ion-option>
                    </ion-select>
                </ion-item>

                <ion-item>
                    <ion-label floating>Bio</ion-label>
                    <ion-textarea formControlName="bio"></ion-textarea>
                </ion-item>

            </form>

        </ion-list>

        <button ion-button full color="primary" (click)="save()">Create Account!</button>

      </ion-slide>

    </ion-slides>

Excuse the giant block of code, but I figured it’d be easier to explain this way. First of all, we’ve added the invalid class conditionally to most elements using something like this:

[class.invalid]="!slideTwoForm.controls.privacy.valid && (slideTwoForm.controls.privacy.dirty|| submitAttempt)"

This will give the element the invalid styling only if the input is invalid (which we can do by checking the valid property on the FormControl) and the user has made an attempt to input a value, or they have attempted to submit the form. If the dirty property is true it means the value has been changed by the user – we don’t want to display errors for every input right away.

We have also added a few items that will display only when an invalid value is supplied to explain to the user what is wrong:

<ion-item *ngIf="!slideOneForm.controls.firstName.valid  && (slideTwoForm.controls.firstName.dirty || submitAttempt)">
    <p>Please enter a valid name.</p>
</ion-item>

Our username field is a bit of a special case, since it asynchronously checks to see if the value is valid (which in our case will always take two seconds) we display a message whilst the value is being checked by checking the pending property on the Control:

<ion-item *ngIf="slideTwoForm.controls.username.pending">
    <p>Checking username...</p>
</ion-item>

Our multi-page signup form should now be fully functional!

Summary

Forms can be quite complex and, although I wouldn’t really call what we’ve done in this tutorial “simple”, I think the FormBuilder, FormControls and Validators available in Angular 2 are an elegant way to handle forms. This really only covers the basics as well, you should be able to do most of what you need with the techniques we discussed in this tutorial, but Controls still offer other functionality we haven’t discussed (for example, it also provides a valueChanges observable that we can subscribe to for changes to an input field).

That’s enough for now though, perhaps we’ll go into even more depth in a future tutorial!

What to watch next...

  • Great article, thanks for sharing! 🙂 I couldn’t help but notice that the part with manually managing the changed state seems a bit awkward though. Angular 1 was a pain with the whole setting the correct validation classes and dirty/touched/pristine/etc business, I would have hoped Angular 2 would be a bit more intuitive than that… There also doesn’t seem to be any core way of doing ng-messages stuff other than what you did above with all the *ngIf conditions. Again, it’s a great way to get over it, although it seems like a bit too much heavy lifting…

  • Matt

    Fantastic post, very helpful, thank you

  • Joe Onyilo

    Your article was spot on. For me, it can at the righ time. It is possible to do the form validation for multiple rows of data. For example, from you sign up form to create two users at once that is creating users in batches not just a single entry.

  • Ken

    Thanks for the article. Is there a way to use the validation directives described in https://angular.io/docs/ts/latest/guide/forms.html with Ionic 2? When I try to do so the styling gets messy as the styles are applied to the Ionic wrapper and the wrapped (and there seems to be no way to prevent the from inheriting the style in this context).

  • samercs

    but in UsernameValidator.checkUsername how can update this method to use inject service to real check username from server ???!!!

  • Ludovic

    Hi thanks for this sample, it’s work Well. But how can we modify validator file for make real call on web server?

    When I use keyword “static” I can’t make call to other function in the file, and I don’t find how I can do this… Thanks for help.

    • soltan

      Same problem here… impossible to use any of my api service nor Ionic http request to check username

    • Arthur Ebing

      Hi Ludovic. Were you ever able to solve this issue? I want to use http inside the method but can’t figure out how to inject it.

  • Lokesh Sahu

    How to prevent hidden field validation.

  • shine

    Thank you for this post! really well written and executed. I am getting an error with TypeScript

    TypeScript error: xxFilePathxx: Error TS2354: No best common type exists among return expressions.

    Not sure what is gong on. Any solutions?

  • Grant

    hey Josh: some edits needed. formGroup replaced ngFormModel

    To avoid the typescript “no best common type exists” error for age validator, wrap execute in Promise…

    • Francesco Mussi

      Can you give an example please? Could be very helpful!

  • Thanks Josh. Can’t get the Validators to work when changing the code using the new @angular/forms directives. Would be really helpful to have the article updated to this new import.

  • Ludovic

    Hi, I have made many seach for found solution. So I hope it’s will be usefull to other… For made real server call for check if a item is avalable or not. Simply use this:

    let serverUsernameValidator = (control) => {
    return this.yourProvider.yourFunction(control);
    };

    this.registerForm = formBuilder.group({
    ‘username’: [”, Validators.compose([Validators.required, Validators.minLength(3), Validators.pattern(‘[a-zA-Z0-9]*’)]), serverUsernameValidator],
    ‘password’: [”, Validators.compose([Validators.required, Validators.minLength(5)])],
    ‘password2′: [”, Validators.compose([Validators.required, Validators.minLength(5)])],
    ’email’: [”, EmailValidator.isValid],
    });

    In your function you can made http call and use

    return new Promise(resolve => {

    this.checkUsernameAvailability(control.value).then((answer) => {
    //alert(answer);
    let answerJson = JSON.parse(answer._body); // convert json strig to object
    //alert(answerJson.status);
    if(answerJson.status === ‘error’) {
    resolve({
    “usernameTaken”: true
    });
    }
    else {
    resolve(null);
    }

    }, (error) => {
    alert(‘server problem’);
    });

    });

    My checkUsernameAvailability made an http call with value and return a json with error.

    Have a nice day

    • Jose Luis

      @ludovic, can you please show the complete code? i tried to get it working without luck 🙁 cant check against server

  • Yen Jacobs

    Can you please update this great tutorial to the latest version?

  • Kealoha Ching

    I had to make some changes to the ion-input fields get this working with Ionic 2 rc. The change for [class.invalid] should also be applied to *ngIf on the invalid display message.

    to

  • Johnny FirstShoe

    Again, maybe it’s because the ionic 2 framework has changed, and made this tutorial obsolete. Because , when I follow this tutorial, I see nothing of what he is saying, eg home.html? Say what? maybe , it’s called index.html? as for modifying it? say what? No idea, what I’m supposed to be modifying?

    • Harry Brand

      You should find it in src/pages/home directory. I hope this helps

  • Francesco Mussi

    I am getting this error:

    error_handler.js:48 EXCEPTION: Error in ./HomePage class HomePage – inline template:27:28 caused by: Cannot read property ‘name’ of undefined

    Coming from this function:

    elementChanged(input){
    let field = input.inputControl.name;
    this[field + “Changed”] = true;
    }

    Apparently input.inputControl doesn’t exists. Do you know how can I fix it please?

  • Hey all – this tutorial has just been updated for RC.3

  • Rodrigo Fernández Romero

    Hey @joshmorony:disqus ! How would you inject a real service (e.g. UserService) in the async validator “app/validators/username.ts”? Thanks for the article.

    • Arthur Ebing

      Hey @rodrigofernndezromero:disqus . Were you able to solve this? I’m new to Ionic 2 and have the same question.

      • Hi @arthurebing:disqus , I did it by passing the service to the validator constructor, like UserValidator(userService).checkUsername(username). Then, at the UserValidator class I added a constructor receiving the userService parameter. From here you can assign it to a class property, like this.userService = userService. Hope it helps!

      • rollcage

        but UsernameValidator.checkUsername is static. how could this work with an constructor???

      • Ah! Now I remember that was the tricky part. I just wrote a small article on Medium expanding on how to inject providers into async validators: https://engineering.savelist.co/injecting-providers-into-async-custom-validators-in-angular-e54a16956d14#.y0shv2f58

      • Jose Luis

        yes, last night fixed it using inject 🙂 thanks for your support @rodrigofernndezromero:disqus

      • tHesTx

        I have somehting like this(in the .ts file where I define the formBuilder):
        this.authForm = formBuilder.group({
        email: [”, Validators.compose([
        Validators.minLength(5),
        Validators.required,
        EmailValidator(authService).isValid])], –> this is not a valid code the “(authService).” part
        password: [”,Validators.required]
        });

        and the validator:
        export class EmailValidator {

        static authServiceStatic: Auth;
        constructor(private authService: Auth){
        EmailValidator.authServiceStatic = authService;
        }

        static isValid(control: FormControl): any {

        EmailValidator.authServiceStatic.loadAllData(“http://localhost:8082/lillytest/retrieve-data.php?table=record”).subscribe(data =>
        {
        if(control.value.toLowerCase() === “greg”){
        return “username taken”;
        } else {
        return null;
        }
        });
        }
        }

        What can I do ?

      • Jose Luis

        I have the same question @rodrigofernndezromero:disqus dont have some code example? im trying to verify while the user is typing into the input if the username is available trough API REST :/

  • Rafael Sánchez Martínez

    Excellent post. I have been following, and great. I have a question though…

    How would I assign initial values to the form component inputs that are coming from a parent component?

    I have tried to assign from an Input() property

    firstName: [ myInputFirstName, Validators.compos…..]
    ….
    ….
    Input() myInputFirstName;

    However, the input property is not already initialized in the constructor. Am I using the right strategy? Please help.

    • Rafael Sánchez Martínez

      OK. I’ve got it.

      Set the value of the form in the ngOnInit() hook:

      ngOnInit()
      {
      this.myFormGroup.setValue(myinputValue);
      }

      Given that myInputValue == {myInputFirstName: ‘foo’}, coming from the parent component, and I have the property

      Input() myInputValue;

      in my form component.

      Thanks!

  • Hy Josh, awesome, what if we want to show more meaningful errors, for example if we only show “Please enter valid name” how user will understand it should be less than 30 chars and it should be only alpha digit?

  • Tony Stark

    how to update “valid” according to our requirement?

  • james

    Is there a way I can synchronize SQLite In Ionic 2 and mysql together.?

  • Muhammad Umair

    Thank you so much .. this make my life easier.

  • John Christian Lønningdal

    I see the custom validators return a string with the error state (true) – is there any way for this text to show up in the actual error on the form? For example in your AgeValidator you have four different cases where it fails with a text – could you get this out into the form error message somehow?

    • OanaLivia

      Quite simple actually 😉
      this.formName.controls[‘formFieldName’].errors will give you the errors.

      For example I have

      this.clientForm = formBuilder.group({

      clientPhone: [”, Validators.compose([Validators.required, PhoneValidator.isValid])],

      })

      My PhoneValidator has several conditions, here is one for example:
      if(control.value.length != 10){
      return {
      “not 10 digits”: true
      };
      }

      Now suppose I leave the clientPhone empty,
      console.log(this.clientForm.controls[‘clientPhone’].errors);
      will output :
      Object {required: true, not 10 digits: true}

      This means that the first group validator (Validators.required) has failed,
      and that the second one (PhoneValidator.isValid) failed too, with the output “not 10 digits”

      Here I console.log this stuff, but you can do whatever you want with it 😉

  • Ana

    Thanks Josh, how can we reference fiels in HTML when using nested groups in FormBuilder?

  • Ana

    Other question, when submiting a form, if validation errors show up, how can we set focus (or scroll) on the invalid input?
    Thanks!

  • napcat

    Hello,

    I was trying to follow this tutorial, but I’m getting an error: “ModalCmp ionViewPreLoad error: No provider for FormGroup!”

    I’m using the lates ionic2 version (2.2.0).

    I also downloaded the sample code from github, and tried to run it. But I’m also getting error when doing ionic serve.

    • Chris VanderKooi

      Are you importing ReactiveFormsModule into your NgModule?

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

      And then:

      @NgModule({
      imports: [ReactiveFormsModule]

  • Johann Taberlet

    Hi, nobody else encounter an issue by using inputs inside slides?
    The keyboard pushes all the app and the inputs are hidden when you start typing.

    I found that issue report but no solution yet https://github.com/driftyco/ionic/issues/9553

    If someone has a walkaround, it will be apreciated! 😉

    • James Malvern

      ditto

    • Maique Rosa

      i’m too…

  • dimitri

    Hi, How would i add a Datepicker for example to this formBuilder. How do you style it if it is ion-native. Could you give an example of DatePicker use since documentation on this is fairly limited how would you and say 2 Datepicker in a fromBuilder .

  • Dave Zuñiga

    Hey @joshmorony:disqus in my app whenever the page my form is in, is pushed the keyboard automatically appears as soon as it loads; I haven’t found a workaround for this issue so far .

  • Amanda

    Hi Josh. Thank you very much another great tutorial. Quick q. If I add a (ngSubmit)=”Submit” in the form tag, it breaks the slider view. Do you know if there a reason for that or is it some kind of bug?

  • Vivek Sinha

    Hi,
    How can I validate credit/debit card numbers in form. I have a card detail form with all basic cc details like card number, cvv, exp month & year. How can I create a validation for this form?

  • Jini Thakker

    https://uploads.disquscdn.com/images/287109223cbe1ddc2e01536accbf74e1b1d69e4f5e8e614b2e6456c0187ae8fb.png For me, the privacy validation is not showing up. I am using ionic 3.2.0. Please advise!

  • bhaumik

    Why should i get this error?

    ERROR RangeError: Maximum call stack size exceeded
    https://puu.sh/wkz7w/01a725c017.png

  • DimaNYC

    How would you create a validator which accepts multiple values? For example I want to have a separate field for a date and another one for time and want to make sure the Date is in the future

  • Ripulryu

    How to call a service in the validator ? Why can’t we include services in construct function of validators ? Why angular/ionic starts giving bizarre errors “length of undefined” ? @Rodrigo thanks for the make shift approach but Angular in itself should have a method to support real time validation. Seems like a bug in Angular itself.

  • Daniel

    Is there any way to check if a FormControl has a Validator.required? for example, to dinamically show some element (* for example) in the input to inform the user that the input is required.

  • Anuj

    Very well detailed explained!!. Awesome job Josh as always.

  • @joshmorony:disqus there is a typo in this. In the “Set up Validation” section, the rundown list has two bullets for “firstName”, the second should be “lastName”

  • Alondra

    Thanks!!! 😀 it has helped me a lot!… regards! :3

  • alejandro cadavid correa

    how can i change the red and blue colors of validation, can anyone help me?

  • Todd

    I’m wondering why go through the hassle of making **custom validators** with fancy error descriptions, if those hard-won validation descriptors are discarded and something else is displayed. Take your age validator, for example– super creative validation errors.

    Or
    “Password must be at least 6 chars long, and have at least one number and special character,” is way more useful than “Please enter a valid password.”

    Am I missing something, having read this quickly? Otherwise, great stuff… Thanks!