Forms and Validations

Handling user input with forms is the cornerstone of many common applications. In this section we added many common use cases of inputs and validations in Ionic apps.

In this Ionic 4 theme we included different types of form validations with Ionic and Angular such as: password and confirm password validation, international phone validation, email format validation and username validator. Learn everything about Forms and Validations in Ionic 4 and how to use Angular Reactive Forms to create your own validators.

Angular Forms

This template uses Angular 8, so we will use Angular Forms library.

Angular provides two different approaches to handling user input through forms: reactive and template-driven. Both capture user input events from the view, validate the user input, create a form model and data model to update, and provide a way to track changes.

Reactive and template-driven forms process and manage form data differently. Each offers different advantages. You can learn more in Introduction to Forms in Angular.

In this Ionic 4 template we use Angular Reactive Forms.

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. Learn more in this Learning Angular Forms Ionic tutorial.

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.

Because we use Reactive Forms, we will define all the logic of the form, the validators and the error messages on the Component.

Form Validators

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 Component. 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.

Basic Validations

Angular provides lots of built-in validators to validate your inputs. In this template we will use the following:

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.

Custom Validators

Since the built-in validators won't always match the exact use case of our applications, sometimes we will need to create a custom validator.

We can get very creative and build any kind of custom validators. In this template we include four useful input validators that you can use through any Ionic or Angular app.

You can find these validators in src/app/validators

custom validators

Phone/Country Validator

The purpose of this one is to validate if a phone number belongs to a certain country.

We use Google Libphonenumber JavaScript library for parsing, formatting, and validating international phone numbers. The phone directive controls that the value from the phone number input is correct for the selected country.

So, for this validation we have a <ion-select> so the user can select the country. In this template we just added a few random countries, however, you can use the countries you need by just adding them to the countries list from the src/app/forms/validations/forms-validations.page.ts or you can also use a library to get all the countries from the world.

forms-validations.page.ts
this.countries = [
new CountryPhone('UY', 'Uruguay'),
new CountryPhone('US', 'United States'),
new CountryPhone('ES', 'España'),
new CountryPhone('BR', 'Brasil'),
new CountryPhone('FR', 'France')
];

We also have an input for the phone number and each time the user changes the selected country, this input updates his placeholder to reflect the correct phone format for that country.

In our component we define a FormGroup with both the country and the phone number. As you can see in the following code the phone number uses our custom invalidCountryPhone validator.

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

Password Validator

This validator is probably the most used and common validator because is present almost in every signup page. It's a validator to check if the confirmation password is equal to the password as you can see in the following image:

password validator

So, for the password input we have the following validations where we check the length and if it contains at least one uppercase and one number.

And then for the FormGroup which contains the password and the confirm_password inputs we validate if they are equal or not.

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]+$')
])),
confirm_password: new FormControl('', Validators.required)
}, (formGroup: FormGroup) => {
return PasswordValidator.areNotEqual(formGroup);
});

Username Validator

The goal of this validator is to check if a username is available or not. In the Forms and Validations page we trigger this validator in real time, I mean, as the user is typing it.

For this template, because we don't have a real database, we define two existing usernames: 'abc123' and '123abc'. If our validation fails, we return an object with a key for the error name and a value of true. Otherwise, if the validation passes, we simply return null because there is no error.

In a real app you should check against your Database if the username is already in use.

To use it in our FormControl we just add it like this:

'username': new FormControl('', Validators.compose([
UsernameValidator.usernameNotAvailable,
// other validators
])),

Error Messages

We want to be able to display custom error messages to our users when they don't fill the form correctly.

error messages

Because we are using Reactive Forms, we don't want to attach the error messages to the markup of the page. To manage error messages, we use an array with all the error types and messages for each control of our ValidationsForm.

Let's see an example with the username input. The username input has five different types of validations and their corresponding error messages.

src/app/forms/validations/forms-validations.page.ts
validations = {
'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: 'usernameNotAvailable', message: 'Your username is already taken.' }
],
// other validations
};

In the markup we have a <ng-container> which iterates through all the validations the username has and checks if it should show the corresponding error message or not.

src/app/forms/validations/forms-validations.page.html
<ion-item class="input-item">
<ion-label position="floating">Username</ion-label>
<ion-input type="text" formControlName="username" clearInput required></ion-input>
</ion-item>
<div class="error-container">
<ng-container *ngFor="let validation of validations.username">
<div class="error-message" *ngIf="validationsForm.get('username').hasError(validation.type) && (validationsForm.get('username').dirty || validationsForm.get('username').touched)">
<ion-icon name="information-circle-outline"></ion-icon>
<span>{{ validation.message }}</span>
</div>
</ng-container>
</div>

Another option for error messages

You could also choose to display errors from the html like in the code below. However, we don't recommend this way because you have the error messages tied to the html of the page. We recommend the way explained before.

<div class="error-container" *ngIf="loginForm.get('email').invalid && (loginForm.get('email').dirty || loginForm.get('email').touched)">
<span *ngIf="loginForm.get('email').errors.required">
Email is required
</span>
<span *ngIf="loginForm.get('email').errors.pattern">
Please enter a valid email address
</span>
</div>