Create and Validate Forms with Ionic

In this tutorial you will learn everything about Ionic forms and input validations in Ionic apps.

We will discuss the best practices for designing user-friendly forms with ionic. Then we will go through the differences between Angular template driven and reactive forms.

For this ionic tutorial we created a mobile app example with lots of forms and validations to help you master data collection using angular reactive forms and also some techniques to excel the user experience.

I will explain you the core concepts of: FormControl, FormGroup, FormBuilder and Validators and show you examples so you can learn to use them properly.

We want this to be the best ionic forms tutorial, so we created advanced custom validators to show you how to validate passwords, phone numbers and unique usernames.

Note: This post is for Ionic 3 and Angular 4, if you are working with Ionic 1 and Angular 1 go to Ionic Form Handling & Validation.

Working With Forms

Forms are the pillar of any business applications. You can use forms to perform countless data-entry tasks such as: login, submit a request, place an order, book a flight or create an account.

When developing a form, it’s important to create a good data-entry experience to efficiently guide the user through the workflow.

Developing good forms requires design and user experience skills, as well as a framework with support for two-way data binding, change tracking, validation, and error handling such as Angular. As you may know, Ionic is built on top of Angular. (if you didn’t know that you should consider reading Ionic Framework introduction and key components).

Forms are usually one of the major interaction points between an app and the user, allowing them to send data to the application. Commonly, the data is sent to the web server but the app can also intercept it to use it on its own.

Let’s keep in mind that the real purpose of forms is data collection. There are different ways of sending and handling that data in the server, and depends a lot on your backend tech stack and the nature of your business logic, so we are not going to cover this. However, if you are interested in building a complete mobile app with Ionic and the MEAN (mongo, express, angular and node) stack, you should check Build a complete mobile app with Ionic 3

Learning Angular Forms

In the following section you will learn how to build an Ionic example app with forms and validations. We will add both basic, advanced and custom form controls. Then we will show you how to add custom error messages for your validations. Remember you can download all the source code using the GET THE CODE button from above.

If you are looking for a super polished user interface and you want to save time by getting a ready made Ionic starter app which features lots of forms and validation examples you should definitely check Ion2FullApp ELITE.

These are some of the forms you can find in Ion2FullApp ELITE. There are also more complex forms such as beautiful filters, ratings, validations, and many more!

Ion2FullApp Ionic Forms
Ion2FullApp Ionic Forms
Ion2FullApp Ionic Forms

Angular Reactive Forms vs Template Driven Forms

Angular offers two form-building technologies: reactive forms and template-driven forms. These two belong to the @angular/forms library and share a series of form control classes. However, they notably diverge in terms of philosophy, programming style and technique. They even have their own modules: ReactiveFormsModule and FormsModule.

Another important difference is that Reactive forms are synchronous while . Template-driven forms are asynchronous.

Before we continue with this ionic example app, let’s clarify what’s the difference between Reactive Forms and Template-driven Forms from a high level perspective.

Angular Reactive Forms

Angular reactive forms helps the reactive programming style that favors an explicit data management flowing between non-UI data models (typically retrieved from a server) and a UI-oriented form model that keeps the states and values of HTML controls on the app screen. Reactive forms offer an easy way to use reactive patterns, testings and validations.

When using reactive forms (also known as model-driven forms), we will avoid directives like ngModel, NgForm, required and such.The idea is that instead declaring that, we actually use the underlying APIs to do it for us and so making Angular power things for us. In a sense, instead binding Object models to directives like in template-driven forms, we create our own instances inside a component class and construct our very own JavaScript models. This approach has a lot more power and is extremely productive to work with since it allows us to write expressive code, that is very testable and keeps all logic in the same place, instead of dividing it around different form templates.

With reactive forms, you are able to create and manipulate form control objects directly in the Component. Since the component class has access to the form control structure and to the data model, you can push data model values into the form controls and pull values that has been changed by the user back out. The component is able to observe changes in the form control state and react to these changes, for example to show a validation message.

One of the advantages of working directly with form control objects is that value and validity updates are always synchronous and under your control. You won’t find the timing issues that sometimes affect a template-driven form. Also, reactive forms can be easier to unit test.

Angular Template driven forms

On the other hand, Template-driven forms take a completely different approach.

You can place HTML form controls (such as <input> or <select>) in the component template and bind them to data model properties in the component, using directives like ngModel.

In template-driven forms, you don’t create Angular form control objects. Angular directives creates them for you using information from your data bindings. You don’t have to push and pull data values because Angular handles that for you through the ngModel directive. Angular updates the mutable data model according to user changes as they happen.

Examples of these directives are the ngModel, required, minlength and so. On an advanced level, this is what template-driven forms achieve for us - by specifying directives to bind our models, values, validations and more, we are letting the template do all the work on the background.

While this approach means less code in the component class, the template-driven forms are asynchronous which may complicate the development in more advanced scenarios.

Template driven forms may resemble more as the way forms used to be in AngularJS (v 1.0), maybe that’s why people still use them as a familiar option.

Which one is the best? Reactive or template-driven? Neither is the best. They’re two different architectural paradigms with their own pros and cons. You can choose the approach that works best for you. This way you are free to decide to use both in the same application.

On this ionic forms tutorial we will use Reactive Forms.

Angular forms basics: FormGroup, FormControl and FormBuilder

As we mentioned before, we are gonna use Angular reactive forms in this example app. These are the basic concepts you need to grasp before we start.

FormControl: it tracks the value and validity status of an angular form control. It matches to a HTML form control such as an input or a selector.

This basic example shows a FormControl for the name property which should not be empty.

ts file:

this.name = new FormControl('Dayana', Validators.required)

html file:

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

FormGroup: it tracks the value and validity state of a FormBuilder instance group. It aggregates the values of each child FormControl into one object, with each form control name as the key. It calculates its status by reducing the statuses of its children. For example, if one of the controls in a group is invalid, the entire group becomes invalid. For example:

this.user = new FormGroup({
   name: new FormControl('Dayana', Validators.required),
   country: new FormControl('Uruguay', Validators.required)
});

FormBuilder: is a helper class that creates FormGroup, FormControl and FormArray instances for us. It basically reduces the repetition and clutter by handling details of form control creation for you.

this.validations_form = this.formBuilder.group({
	name: new FormControl('', Validators.required),
	email: new FormControl('', Validators.compose([
		Validators.required,
		Validators.pattern('^[a-zA-Z0-9_.+-][email protected][a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')
	]))
});

All of them should be imported from the @angular/forms module.

import { Validators, FormBuilder, FormGroup, FormControl } from '@angular/forms';

How to design great user experience for your ionic app forms?

Great UX = Improve conversions

Forms are also the bottleneck of the conversion funnel, as they require the user to perform a task much more complicated than just a click, by thinking and typing all the required information by the form. This may be much more frustrating in mobile devices and narrow screens, which is why you have to strive to do your best to have an awesome user experience to reduce users frustrations and ultimately improve your funnel conversion.

Some people say that user experience is often more important than what the app offers itself. So before starting coding, it’s important to take the time and analyze which fields are needed. When designing the mock up, always try to include the fields that are imperative to you, knowing that the bigger the form, the bigger chance you have of losing some users. Keep in mind the simplicity by only asking for what you absolutely need.

It isn’t part of the article scope to go beyond the basics of UX of forms, but if you want to learn more about designing better forms, we recommend these articles:

From a design perspective, Ionic Framework has a wide collection of nice form elements, and form inputs for you to choose. On the other hand, from the UX point of view, we are going to use Angular form validations to enhance the experience of our ionic mobile app.

A quick enhancement that can be easily implemented into your Ionic app is the form validation. The best practices say that you should always validate all user inputted data via backend and we completely agree with that statement. However, by validating via frontend it can make improvements to the user experience and perceived response time.

Angular ships with its own validators that they work great in Ionic apps. We are going to check out how to use a few of the Angular form validators to make our app significantly better for users.

Validation timing - When should we perform the form validation?

The key is to avoid interrupting and annoying the user. That’s why it’s not recommended to validate when the user submits the form. Imagine that panorama, the user spent time filling in each input (without knowing the constraints beforehand) and finally when he thinks the task is done, you show him multiple error messages caused by invalid inputs. Frustrating!

It’s much better to consider these options:

  • Real time: It means validating as you type. These are super handy and are the default for Angular Validators.

  • On blur: It may be less annoying for certain cases as we only validate when the user focus out from the form input. However, the user will only see the errors once he moves to the next input.

Designing our Form UX and Input Validation requirements

The goal of this post is to show you how to achieve ionic form validations so we will be going through as many form validation examples as possible. It’s important to take some time to write down your form and validation requirements before starting, in our case this is the data we want to collect with its corresponding constraints.

  • Username
    • Min length (5)
    • Max length (25)
    • Must contain numbers and letters
    • The username must be unique
    • Required
  • Name
    • Required
  • Last name
    • Required
  • Email
    • Valid email
    • Required
  • Country selection
    • Valid country from a dropdown
  • Phone
    • The phone must be valid for the selected country
    • Should accept only numbers
    • Required
  • Gender
    • Pick one of the provided list
  • Password
    • Min length (5)
    • Must contain letters (both uppercase and lowercase) and numbers
    • Must re-type password to ensure correctness
    • Required
  • Terms Checkbox
    • Must accept terms

Ionic Forms and Validations example App

Let’s put in practice what we have learned so far and start building our Ionic form handling and validations example app. This is what we are going to build in this section:

Forms and Validation in ionic
Forms and Validation in ionic

In reactive forms, instead of adding validators through attributes in the template (like we do in template driven forms), you add validator functions directly to the form control model in the angular component class. Angular then calls these functions whenever the value of the control changes.

You can choose to write your own validator functions, or you can use some of Angular built-in validators.

Built-in validators are stock validators provided by Angular. For a full list of Angular built-in validators, see the Validators API reference. However built-in validators won't always match the exact use case of your application, so sometimes you will want to create a custom validator for your ionic app.

In this ionic forms and validation tutorial we will explain you how to create and use both types of validators.

Basic Ionic form input validations

In this ionic tutorial we will use the following Angular built-in validators to validate our form inputs:

required: Validator that requires controls to have a non-empty value. It also validates that the value matches the input type. For example, if the input is of “email” type, then the input will be valid if it’s not empty and if the value is of email type.

minLength: Validator that requires controls to have a value of a minimum length.

maxLength: Validator that requires controls to have a value of a maximum length.

pattern: Validator that requires a control to match a regex to its value. You can find more information about regex patterns in the PatternValidator reference.

email: Validator that performs email validation.

compose: is used when more than one validation is needed for the same form field.

Let’s start with some easy validations for the name, last name and email inputs. As we defined before, we have the following requirements for our form controls:

  • Name: required
  • Last name: required
  • Email: valid email and required
  • Terms: must accept terms and conditions (checkbox checked validation)

So we will create a FormControl for each of them. Check the following typescript code to see how you can achieve this.

name: new FormControl('', Validators.required),
lastname: new FormControl('', Validators.required),
email: new FormControl('', Validators.compose([
	 Validators.required,
	 Validators.pattern('^[a-zA-Z0-9_.+-][email protected][a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')
 ])),
terms: new FormControl(true, Validators.pattern('true'))

Advanced Ionic form controls and custom validations

We can get very creative and build any kind of custom validators. In this post we will be implementing three in particular. One to validate that the username is unique (checks against the server if the username is already taken), another one that implements the confirm password validation to ensure the user typed the desired password correctly, and a third one that validates that the phone is valid for the selected country.

Ionic username custom validator

The goal of this custom angular validator is to validate that the username is available. We added the following code in a new username.validator.ts file:

import { FormControl } from '@angular/forms';
export class UsernameValidator {
  static validUsername(fc: FormControl){
    if(fc.value.toLowerCase() === "abc123" || fc.value.toLowerCase() === "123abc"){
      return ({validUsername: true});
    } else {
      return (null);
    }
  }
}

With fc.value we obtain the value in the username field, so we can control the usernames we won't allow to enter.

Then, in the typescript file where we have defined our form, we should import our custom validator.

import { UsernameValidator } from '../../validators/username.validator';

this.validations_form = this.formBuilder.group({
  username: new FormControl('', Validators.compose([
		UsernameValidator.validUsername,
		Validators.maxLength(25),
		Validators.minLength(5),
		Validators.pattern('^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$'),
		Validators.required
	])),
})

We used minLength(5) and maxLength(25) to ensure the minimum and maximum length of the value. We also used required to avoid this input to be left empty, and ng-pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$" to force a correct input value containing letters and numbers.

Notice how we used the UsernameValidator.validUsername custom directive to validate that the username is not already taken. This is the implementation of the custom directive, as we don’t have a dedicated backend and database for this ionic app example, we hardcoded two existing usernames (“abc123” and “123abc”).

Ionic phone and country advanced validator

The purpose of this custom angular validator is to validate that a phone number belongs to a certain country.

We will use Google Libphonenumber JavaScript library for parsing, formatting, and validating international phone numbers. To install it you should run:

npm install --save-prod google-libphonenumber

We added the following code in a new phone.validator.ts file to create our PhoneValidator:

import { AbstractControl, ValidatorFn } from '@angular/forms';
import libphonenumber from 'google-libphonenumber';

export class PhoneValidator {

	 // Inspired on: https://github.com/yuyang041060120/ng2-validation/blob/master/src/equal-to/validator.ts
	static validCountryPhone = (countryControl: AbstractControl): ValidatorFn => {
    let subscribe: boolean = false;

    return (phoneControl: AbstractControl): {[key: string]: boolean} => {
      if (!subscribe) {
        subscribe = true;
        countryControl.valueChanges.subscribe(() => {
          phoneControl.updateValueAndValidity();
        });
      }
      if(phoneControl.value !== ""){
        try{
          const phoneUtil = libphonenumber.PhoneNumberUtil.getInstance();
          let phoneNumber = "" + phoneControl.value + "",
              region = countryControl.value.iso,
              number = phoneUtil.parse(phoneNumber, region),
              isValidNumber = phoneUtil.isValidNumber(number);
          if(isValidNumber){
            return null;
          }
        }catch(e){
          return {
            validCountryPhone: true
          };
        }
        return {
          validCountryPhone: true
        };
      }
      else{
        return null;
      }
    };
  };
}

The phone directive controls that the value from the phone number input is correct for the selected country.

Then, in the typescript file where we have defined our form (form.ts), we should import our custom validator. In form.ts we have a FormGroup with the phone input and the country selector so when the value changes in one of these fields the directive checks if both are correct.

this.country_phone_group = new FormGroup({
		country: new FormControl(this.countries[0], Validators.required),
		phone: new FormControl('', Validators.compose([
		Validators.required,
		PhoneValidator.validCountryPhone(country)
	]));
});

Ionic password validation

Let’s continue with the password form control validations. As we mention before, we have the following requirements for our password input form:

  • Required
  • Min length (5)
  • Must contain letters (both uppercase and lowercase) and numbers
  • Must re-type password to ensure correctness

We added the following code in a new password.validator.ts file to create our PasswordValidator which validates that the password was re-typed correctly:

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

export class PasswordValidator {
// Inspired on: http://plnkr.co/edit/Zcbg2T3tOxYmhxs7vaAm?p=preview
static areEqual(formGroup: FormGroup) {
	let val;
	let valid = true;

	for (let key in formGroup.controls) {
		if (formGroup.controls.hasOwnProperty(key)) {
			let control: FormControl = <FormControl>formGroup.controls[key];
			if (val === undefined) {
				val = control.value
			} else {
				if (val !== control.value) {
					valid = false;
					break;
				}
			}
		}
	}
	if (valid) {
		return null;
	}
	return {
		areEqual: true
	}
 }
}

Then, in the typescript file where we have defined our form (form.ts), we should import our custom password validator and add the other simple validations: password no shorter than 5 chars, and with letters and numbers.

import { PasswordValidator } from '../../validators/password.validator';
this.matching_passwords_group = new FormGroup({
	password: new FormControl('', Validators.compose([
	 	Validators.minLength(5),
	 	Validators.required,
	 	Validators.pattern('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9]+$') //this is for the letters (both uppercase and lowercase) and numbers validation
	])),
	confirm_password: new FormControl('', Validators.required)
}, (formGroup: FormGroup) => {
	 return PasswordValidator.areEqual(formGroup);
});

Ionic Form Validations Error messages

From a UX perspective, it is also very important to display proper error messages to guide the user in the correct direction.

We will define a list of possible error messages in the typescript file where we have defined our form (form.ts).

validation_messages = {
'username': [
		{ type: 'required', message: 'Username is required.' },
		{ type: 'minlength', message: 'Username must be at least 5 characters long.' },
		{ type: 'maxlength', message: 'Username cannot be more than 25 characters long.' },
		{ type: 'pattern', message: 'Your username must contain only numbers and letters.' },
		{ type: 'validUsername', message: 'Your username has already been taken.' }
	],
	'name': [
		{ type: 'required', message: 'Name is required.' }
	],

.... //more messages
}

Then, in our HTML file we must iterate the list and show the right message.

	<ion-item>
	<ion-label floating color="primary">Username</ion-label>
	<ion-input type="text" formControlName="username" class="form-control"></ion-input>
	</ion-item>
	<div class="validation-errors">
	<ng-container *ngFor="let validation of validation_messages.username" >
		<div class="error-message" *ngIf="validations_form.get('username').hasError(validation.type) && (validations_form.get('username').dirty || validations_form.get('username').touched)">
	{{ validation.message }}
		</div>
	</ng-container>
	</div>
	

Keep learning Ionic

In this ionic tutorial we showed you how to handle Forms and Validations in Ionic. Now that you’ve learned more about data collection using forms and some techniques to excel the user experience, you can focus on

Did you know that we also offer beautiful mobile templates and components that you may find super useful as they save you hours of development, time and effort, while giving your projects a great design from scratch?

Why don’t you give a chance to these Ionic templates and apply this new knowledge?