Tuesday 14 August 2018

Angular 5 Forms and Validations

We created this angular forms tutorial to help you learn everything about Angular forms validations in angular 5 apps. These angular forms examples are updated using the best (coding) practices to build Angular 5 apps with Material Design.
We will go through the best practices to design user-friendly forms and validations using angular. After that we will go through the differences between each Angular forms; template driven forms and reactive forms.
For this angular 5 tutorial we built an example project with different forms and validations to help you understand everything about data collection using angular reactive forms. Also we will explore some techniques to improve the user experience of your angular forms.
We want this to be an excellent angular forms tutorial, so we created advanced custom validators to show you how to validate passwords, phone numbers and unique usernames. Of course we will first explain about some basic angular form validation examples such as: how to use an email validation pattern to validate your email inputs.
If this is your first time using angular reactive forms, this tutorial will help you understand the core concepts and fundamentals of angular FormControl, angular FormGroup, angular FormBuilder, the different types of built-in validators and how to create your custom validators.

Working With Forms

Forms are almost always present in any website or application. Forms can be used to perform countless data-entry tasks such as: authentication, order submission or a profile creation.
Building easy-to-use 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 :)
If you need to go deeper about angular main building blocks as well as the best practices for building a complete app with Angular you can check this Angular Tutorial: Learn Angular from scratch step by step
Let’s keep in mind that the real objective of forms is gathering data. As it always happens in software, there are many different ways of sending and handling data in your server. This depends a lot on the backend and the nature of your business logic, so we are not going to cover this topic in this tutorial. However, if you want to use the MEAN stack (Mongo, Express, Angular and Node), you should read Building an Angular example app step by step.
Remember you can download all the source code using the GET THE CODE button from above.

Angular Forms Fundamentals

Template Driven Forms vs Angular Reactive Forms

When it comes to form-building, Angular offers two 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 deviate in terms of philosophy and programming technique. They use their very own modules: ReactiveFormsModule and FormsModule.
There is an important difference between them and that is: Reactive forms are synchronous while Template-driven forms are asynchronous.
Before we continue with our example app using angular reactive forms, let’s define the difference between reactive and template-driven forms from a high level perspective.

Angular Reactive Forms

Angular reactive forms, also known as model-driven forms, offers an easy way to use reactive patterns and validations. They follow the reactive programming style that supports an explicit data management flow between non-UI data models (frequently retrieved from a server) and a UI-oriented form model that keeps the states and values of HTML controls on the app screen.
When coding reactive forms, we will avoid directives like required, ngModel, NgForm and such. The idea is that we actually use the underlying APIs to do it for us. In a sense, instead binding Object models to directives like it happens in template-driven forms, we create our own instances inside a component class and build 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 (a very testable one that keeps all the logic in the same place) instead of dividing it over different form templates.
With reactive forms, you will be able to create and manipulate form control objects directly in the Component. Since the component class has access to the form control structure and the data model, you can push data model values into the form controls as well as pull values that have been changed by the user. The component is able to observe changes in the form control state and react to them. This is specially useful for showing a validation message.
One of the advantages that working directly with form control objects brings you 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 tend to be easier to unit test.

Angular Template driven forms

On the other hand, Template-driven forms have a different approach.
You can place HTML form controls (such as <input> or <select>) in the component template and bind them to data model properties, using directives like ngModel.
With template-driven forms, you don’t create Angular form control objects. They are created by Angular directives using information from your data bindings. You don’t have to push and pull data values around because Angular handles that for you through the ngModel directive. Angular updates the mutable data model according to user changes as they occur.
A few examples of these directives are; ngModel, required, maxlength. In template-driven forms we specify directives to bind our models, values, validations and more, so we are actually letting the template do all the work on the background.
Although 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 how forms used to be in AngularJS (v 1.0), probably, that’s why people still use them as a familiar option.
Which one is the best? Reactive or template-driven?
Neither one. They are two different architectural paradigms with their own advantages and disadvantages. You can choose the approach that suits you the best. This way you are free to even decide using both in the same application.
On this angular 5 forms tutorial we will be using Reactive Forms.

Angular forms building blocks: FormControl, FormGroup and FormArray

In this example app, we are going to work using Reactive Forms. These are the basic concepts you need to understand before we start.
FormControl: it tracks the value and validity status of an angular form control. It matches to a HTML form control like an input.
The following is an example which shows a FormControl for the ‘name’ property which should not be empty.
this.username = new FormControl('agustin', Validators.required);
<ion-input type="text" formControlName="username"></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, using the name of each form control as the key. It calculates its status by reducing the statuses of its children. If one of the controls inside a group is invalid, the entire group becomes invalid.
this.user_data = new FormGroup({
   username: new FormControl('agustin', Validators.required),
   city: new FormControl('Montevideo', Validators.required)
});
FormArray: is a variation of FormGroup. The main difference is that its data gets serialized as an array, as opposed to being serialized as an object in case of FormGroup. This might be especially useful when you don’t know how many controls will be present within the group, like in dynamic forms.
this.user_data = new FormArray({
   new FormControl('agustin', Validators.required),
   new FormControl('Montevideo', 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({
 username: new FormControl('', Validators.required),
 email: new FormControl('', Validators.compose([
  Validators.required,
  Validators.pattern('^[a-zA-Z0-9_.+-]+@[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';
Angular is packed with its own validators. In a moment we are going to check out how to use some of the Angular form validators to make our app significantly better for our users.

Forms UX to increase conversions

Forms are also a bottleneck of the conversion funnel, because they require the user to perform a task much more complex than just a click, by thinking and typing all the required information by the form. This may be frustrating when you type all the information and, in the end, you need to retype all over again, which is why you have to strive to do your best to have an awesome user experience to reduce users frustrations and conclusively improve your funnel conversion.
Some people say that user experience is often more important than what the app offers itself. So before we start coding, it’s important to take some time to analyze which fields are really needed. When designing the mock up, always try to include the fields that are essential to you, knowing that the bigger the form, the bigger chance you have of losing some users.
Keep in mind that simplicity is well appreciated, ask only for what you precisely need.
From a design perspective, Angular Material has a wide collection of nice form elements and form inputs for you to choose. From the UX point of view we will make use of angular form validations to enhance the experience of our angular website app.
Best practices say you should always validate all user inputted data via backend and we agree with that statement. However, by also validating via frontend, you can improve the user experience and perceived response time.

When should we validate our forms?

The most important thing is to avoid interrupting and annoying the user. That’s why it’s not recommended to validate right just when the user submits the form. Imagine this scenario, the user spent time filling each input (without knowing the constraints beforehand) and finally when he thinks the task is done, multiple error messages are shown due to invalid inputs. This is frustrating.
Instead, we should consider these options:
Real time validation: It means validating as you type. These are super handy and are the default for Angular Validators.
On blur validation: 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. As we can see in the official documentation Angular 5 has a new option for your ngModel updateOn: 'blur' and you can use it like this:
this.email = new FormControl(null, {
 validators: Validators.required,
 updateOn: 'blur'
});

Angular Material for the win

Angular Material is a project developed by Google which goal is to build a set of high-quality UI components built with Angular and TypeScript, following the Material Design specifications. These components serve as an example of how to write Angular code following best practices. You can use these components in your angular apps very easily.
In our forms and validation example project we are using the following Material components: form fields, inputs, date picker, checkbox, select and buttons.

Designing our form and validation requirements

The goal of this tutorial is to explain how to master angular form validations so we will be going through many form validation examples. It’s important to take some time to write down your form and validation requirements before starting. Below, we will show the data we want to collect with its corresponding constraints.
Angular Forms and Validations example
For this angular forms and validations example we created two separate forms: user details and account details.
User details form components and constraints:
Input NameInput TypeInput ValidationsValidation Type
BioText area1. can’t be more than 256 characters long1. Maxlength 256
BirthdayDate picker1. not empty1. required
GenderSelect1. not empty1. required
CountrySelect1. not empty1. required
PhoneTelephone1. not empty 
2. valid for the selected country
1. required 
2. custom validation
Full NameText1. not empty1. required
Account details form components and constraints:
Input NameInput TypeInput ValidationsValidation Type
User NameText1. not empty
2. at least 5 characters long
3. can’t be more than 25 characters long
4. must contain only numbers and letters
5. unique in our system
1. required
2. minlength 5
3. maxlength 25
4. pattern validation
5. custom validation
Emailemail1. not empty
2. valid email
1. required
2. pattern validation
Passwordpassword1. not empty
2. at least 5 characters long
3. must contain at least one uppercase,
one lowercase, and one number
1. required
2. minlength 5
3. pattern validation
Confirm passwordpassword1. not empty
2. Equal to password
1. required
2. custom validation
Termscheckbox1. accepted1. pattern validation
In Angular Admin Template we have lots of different inputs and validations that you can use in your angular project. There are basic and also advanced forms and validations that you should definitely check in Angular Admin Template.
Angular forms

Angular Forms and Validations example

Let’s put in practice what we have learned so far and start building our angular 5 form handling and validations example app. The following image is how our final angular 5 example will look like.
Angular Forms and Validations example
In reactive forms, instead of adding validators through attributes in the template (like it happens with template driven forms), you add validator functions directly to the form control model in the angular component class. Angular will call these functions whenever the value of the control changes.
You can choose between writing your own validator functions and using some of the Angular built-in validators.
Built-in validators are stock validators provided by Angular. For a full list of Angular built-in validators, here you can 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 angular website.

Built in Angular input validations

We will use the following Angular built-in validators to validate our form inputs:
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.
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.
Let’s start with some name and email inputs validations. These are the requirements for our form controls:
  • Full name: required
  • Email: valid email and required
  • Terms: must accept terms and conditions (checkbox checked validation)
We will define our User Details Form with a FormGroup. To create the FormGroup we will use an instance of the FormBuilder. Check the following typescript code to see how you can achieve this. Note that for the full name and bio we preloaded some default data. You can find this code in src/app/form-component/form.component.ts
// user details form validations
this.userDetailsForm = this.fb.group({
  fullname: ['Homero Simpson', Validators.required ],
  bio: ["Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s", Validators.maxLength(256)],
  birthday: ['', Validators.required],
  gender: new FormControl(this.genders[0], Validators.required),
  country_phone: this.country_phone_group
});
Now we have to create the form, inputs and their error messages inside our template file. Below is the html code for defining the form and the name input. Download the source code (click the GET THE CODE button from the beginning of this page) of this angular 5 forms and validations example to see all the inputs. We didn’t put it here because we don’t want to make this tutorial super large.
You can find this code in src/app/form-component/form.component.html
<form [formGroup]="userDetailsForm" (ngSubmit)="onSubmitUserDetails(userDetailsForm.value)">
    <mat-form-field class="full-width">
      <input matInput placeholder="Full Name" formControlName="fullname" required>
      <mat-error *ngFor="let validation of validation_messages.fullname">
        <mat-error class="error-message" *ngIf="userDetailsForm.get('fullname').hasError(validation.type) && (userDetailsForm.get('fullname').dirty || userDetailsForm.get('fullname').touched)">{{validation.message}}</mat-error>
      </mat-error>
    </mat-form-field>
 <button class="submit-btn" color="primary" mat-raised-button type="submit" [disabled]="!userDetailsForm.valid">Submit</button>
</form>
All the mat tags are components from angular material like mat-error which is the way to display the error messages in our mat-form-field.
<mat-form-field> is a component used to wrap several Angular Material components and apply common styles such as underline, floating label, and hint messages. Read more in the official documentation.

Angular email validation example

To validate the email we have to use a pattern type validator. The email validation pattern we are going to use is ^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$
So we have to create our FormGroup like we explained above and create a FormControl to define our email form control with its validations.
this.accountDetailsForm = this.fb.group({
 email: new FormControl('', Validators.compose([
   Validators.required,
   Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')
 ])),
 // more form inputs
})
Then, in the template file, the html looks just like the other inputs code with a mat-form-fieldenclosing the input and the mat-error.
<mat-form-field class="full-width">
  <input matInput type="email" placeholder="Email" formControlName="email" required>
  <mat-error *ngFor="let validation of account_validation_messages.email">
    <mat-error class="error-message" *ngIf="accountDetailsForm.get('email').hasError(validation.type) && (accountDetailsForm.get('email').dirty || accountDetailsForm.get('email').touched)">{{validation.message}}</mat-error>
  </mat-error>
</mat-form-field>
I forgot to mention that we have defined our error messages also in our src/app/form-component/form.component.ts file. Each input can have more than one validation that’s why we created an array of validation messages for each form input.
For example for our Account Details form we have the following error messages:
account_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' }
],
'email': [
  { type: 'required', message: 'Email is required' },
  { type: 'pattern', message: 'Enter a valid email' }
],
'confirm_password': [
  { type: 'required', message: 'Confirm password is required' },
  { type: 'areEqual', message: 'Password mismatch' }
],
'password': [
  { type: 'required', message: 'Password is required' },
  { type: 'minlength', message: 'Password must be at least 5 characters long' },
  { type: 'pattern', message: 'Your password must contain at least one uppercase, one lowercase, and one number' }
],
'terms': [
  { type: 'pattern', message: 'You must accept terms and conditions' }
]
}
Then in our mat-error we iterate through the error messages of each specific input. Let’s go back to the email validation example. We iterate the account_validation_messages.email messages and for each of them we show it only if the form control has an error of that type and the control is dirty or touched. What does that mean? It means that the user has already touched the input.
<mat-error *ngFor="let validation of account_validation_messages.email">
 <mat-error class="error-message" *ngIf="accountDetailsForm.get('email').hasError(validation.type) && (accountDetailsForm.get('email').dirty || accountDetailsForm.get('email').touched)">{{validation.message}}
 </mat-error>
</mat-error>

Advanced angular form controls and custom validators

We can get very creative and build any kind of custom validators. In this tutorial we will be implementing three in particular.
  • Validate that the username is unique (checks against a fake server if the username is already taken).
  • Validate that the confirm password input value is equal to the password input value.
  • Validate if the phone is valid for the selected country.

Angular custom validator example for the username

Let’s start with the easiest one. The goal of this custom angular validator is to validate if a username is available or not. This simple implementation doesn’t allows the username to be abc123 or 123abc. We don’t have a dedicated backend and database for this angular app example so we hardcoded two fake existing usernames just to show you how to achieve this validator.
Note that in a real application you will have to check this validator against your username database.
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.

Angular custom validator for a phone number

The goal of this custom angular validator is to validate that a phone number is valid for 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 custom phone validator:
import { AbstractControl, ValidatorFn } from '@angular/forms';
import * as 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 = 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();
          const phoneNumber = '' + phoneControl.value + '';
          const region = countryControl.value;
          const pNumber = phoneUtil.parseAndKeepRawInput(phoneNumber, region.iso);
          const isValidNumber = phoneUtil.isValidNumber(pNumber);

          if (isValidNumber) {
            return undefined;
          }
        } catch (e) {
          console.log(e);
          return {
            validCountryPhone: true
          };
        }

        return {
          validCountryPhone: true
        };
      } else {
        return undefined;
      }
    };
  }
}
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)
 ]));
});

Angular password validation

Let’s continue with the password form control validation. As mentioned before, we have the following requirements for the password input form:
  • Required
  • Min length (5)
  • Must contain letters (both uppercase and lowercase) and numbers
  • User 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. It’s like a password match validator.
import { FormControl, FormGroup, NgForm, FormGroupDirective } from '@angular/forms';

export class PasswordValidator {
  // Inspired on: http://plnkr.co/edit/Zcbg2T3tOxYmhxs7vaAm?p=preview
  static areEqual(formGroup: FormGroup) {
    let value;
    let valid = true;
    for (let key in formGroup.controls) {
      if (formGroup.controls.hasOwnProperty(key)) {
        let control: FormControl = <FormControl>formGroup.controls[key];

        if (value === undefined) {
          value = control.value
        } else {
          if (value !== 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);
});
And then in for the html of this password inputs we do the following:
<div formGroupName="matching_passwords">
  <mat-form-field class="full-width">
    <input matInput type="password" placeholder="Password" formControlName="password" required>
    <mat-error *ngFor="let validation of account_validation_messages.password">
      <mat-error class="error-message" *ngIf="accountDetailsForm.get('matching_passwords').get('password').hasError(validation.type) && (accountDetailsForm.get('matching_passwords').get('password').dirty || accountDetailsForm.get('matching_passwords').get('password').touched)">{{validation.message}}</mat-error>
    </mat-error>
  </mat-form-field>

  <mat-form-field class="full-width">
    <input matInput type="password" placeholder="Confirm Password" formControlName="confirm_password"  [errorStateMatcher]="parentErrorStateMatcher" required>
    <mat-error *ngFor="let validation of account_validation_messages.confirm_password">
      <mat-error class="error-message" *ngIf="(accountDetailsForm.get('matching_passwords').get('confirm_password').hasError(validation.type)|| accountDetailsForm.get('matching_passwords').hasError(validation.type)) && (accountDetailsForm.get('matching_passwords').get('confirm_password').dirty || accountDetailsForm.get('matching_passwords').get('confirm_password').touched)">{{validation.message}}</mat-error>
    </mat-error>
  </mat-form-field>

</div>
Noticed the error state matcher in the confirm password input? We created a ParentErrorStateMatcher which implements from ErrorStateMatcher and its goal is the validate that the form group is valid. In this case the form group includes all the password input validations and the confirm password input validations.

Keep learning Angular

In this angular 5 forms and validations tutorial you learned how to handle forms in angular and also how to validate your form inputs. Now that you’ve experienced more about data collection using forms and some techniques to create your custom validators, you can continue with:
Hopefully, you didn't run into any issues with these Angular Forms and Validations examples, but if you did, feel free to post in the comments section below. Remember you can get the full source code of this Angular 5 app by clicking the GET THE CODE button from the beginning of this page.
If you want to build a complex and robust web app with Angular you should check Angular Admin Template which is the most complete and advanced Angular 5 Template with lots of components and performance improvements. It includes features such as Angular Universal, AOT (ahead of time compilation), Material Design, Lazy Loading Routes and lots of useful and responsive components.

No comments:

Post a Comment

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...