Validations for Template-driven forms in Angular 4


Prerequisite : Basic understanding of Angular 2/4

First of all, we need to create a simple form in Angular.

You can clone this repository for the example. We’ll continue further with this example.

This is the form that we have in AppComponent :

<div class="container">
    <div class="row">
        <form #myForm="ngForm" (ngSubmit)="submitForm()">

            <div class="row">
                <div class="col-md-4 col-md-offset-4">
                    <h1>Form Validations</h1>
                </div>
            </div>

            <div class="row">
                <div class="col-md-4 col-md-offset-4">
                    <input type="text" name="email" [(ngModel)]="email" class="form-control" placeholder="email"/>
                </div>
            </div>

            <div class="row">
                <div class="col-md-4 col-md-offset-4">
                    <input type="text" name="password" [(ngModel)]="password" class="form-control" placeholder="password"/>
                </div>
            </div>

            <div class="row">
                <div class="col-md-4 col-md-offset-4">
                    <button class="btn btn-primary" type="submit">Submit</button>
                </div>
            </div>

        </form>
    </div>
</div>

Here, we have two input fields, that are bound to two properties, email and password, so let’s create them in our component as follows:

email: string = '';
password: string = '';

Also, we have used ngSubmit directive, to call submitForm() method on form submit. In reality this method will probably call a service method to make an HTTP request, but for now, let’s create a temporary method.

submitForm() {
  alert('email : ' + this.email + '\npass : ' + this.password)
}

Now, it should look something like this :

Selection_034

Without any validations, you’ll be able to submit the form. Let’s add some validations.

HTML validations :

Update the input fields with HTML validations, add required to both of the input fields, change type to email and password respectively. Then add minlength=”8″ in password field and add email in the email field. They should look like :

<input type="email" required email name="email" [(ngModel)]="email" class="form-control" placeholder="email"/;>
<input type="password" required minlength="8" name="password" [(ngModel)]="password" class="form-control" placeholder="password"/;>

Also, update the button with the disabled directive.

<button [disabled]="!myForm.form.valid" class="btn btn-primary" type="submit">Submit</;button>

Here, myForm is the template reference variable, to which we have assigned the ngForm directive(look at the form tag). This directive gives the variable some properties that we can use, one of them is the boolean property “myForm.form.valid”, which tells if the form is valid or not. Depending on that we disable or unable the button.

Now, your button will be disabled until the values in input fields are valid.

Let’s add the validation messages for the fields.

Validation messages :

Add the following object to your Component :

formErrors = {
  'email': '',
  'password': ''
};

Create a file named app.messages.ts in the “app/” directory, and add the following content :

export  class AppValidationMessages {
    static errorMessages = {
        'email' : {
            'email' : 'Please enter a valid email address',
            'required' : 'Email address is required'
        },
        'password' : {
            'required' : 'Password is required',
            'minlength' : 'Password must be at least 8 characters',
        }
    };
}

To show these messages dynamically,

First, we need to access myForm in the component. We can do that using ViewChild.

@ViewChild('myForm') currentForm: NgForm;
myForm: NgForm;

Now, the currentForm variable has the reference of myForm(template reference variable). Also we have created another variable of type NgForm named “myForm”.

Let’s add an Angular lifecycle hook, to detect any change in the template, for that we can use AfterViewChecked lifecycle hook. For more explanations on lifecycle hooks, refer this documentation.

  • Import AfterViewChecked
import {AfterViewChecked} from '@angular/core';
  • Implement it
export class AppComponent implements AfterViewChecked{
  • Override ngAfterViewChecked()
ngAfterViewChecked() {
  this.formChange();
}

formChange() {
  if (this.currentForm === this.myForm) { return; }

  this.myForm = this.currentForm;
  if (this.myForm) {
    this.myForm.valueChanges
        .subscribe(data => this.onValueChanged());
  }
}

Here we check if our NgForm variable i.e. myForm is up to date to the current form, if not we update it, and then subscribe to one of its properties, i.e. valueChanges, as the name suggests, returns resolved Observable if there is a change in any value of the form. Therefore, on any change of value in form, we call onValueChanged() method.

onValueChanged() {
  if (!this.myForm) { return; }
  const form = this.myForm.form;

  for (const field in this.formErrors) {
    this.formErrors[field] = '';
    const control = form.get(field);

    if (control && control.dirty && !control.valid) {
      const messages = AppValidationMessages.errorMessages[field];
      for (const key in control.errors) {
        this.formErrors[field] = messages[key];
      }
    }
  }
}

Here, we traverse through the formErrors object that we created in the component via its keys. Get the control for each key. The form.get(‘key’) gives the control element of the form corresponding to the key like email or password in this case(your element i.e. input element in the template should have the same name as that of key).

Then checks if that control is defined or not, its dirty property is true or false and if its valid or not. The control is marked dirty if it has been changed once. These properties dirty, valid, invalid and many more are added to the control via ngModel directive that we used in every input element. To know more about these properties, refer this documentation.

If any control is changed and if its not valid, we get all error messages of that control in an object named “messages“, and depending upon what’s the error on the control, we update the formErrors object with the following message.

Now, all we need to do is show that message if there is one, so below the input controls, we add that message with a simple *ngIf :


<div class="row">
    <div class="col-md-4 col-md-offset-4">
        <input type="email" email required name="email" [(ngModel)]="email" class="form-control" placeholder="email"/>
    </div>
    <div class="col-md-4 col-md-offset-4">
        <div *ngIf="formErrors.email" class="alert alert-danger">{{formErrors.email}}</div>
    </div>
</div>

<div class="row">
    <div class="col-md-4 col-md-offset-4">
        <input type="password" minlength="8" required name="password" [(ngModel)]="password" class="form-control" placeholder="password"/>
    </div>
    <div class="col-md-4 col-md-offset-4">
        <div *ngIf="formErrors.password" class="alert alert-danger">{{formErrors.password}}</div>
    </div>
</div>

Now your form must look like this :

Selection_035

Don’t want to disable the button?

Yes, I hate the approach that Angular’s documentation follow, i.e. to disable the button depending upon if the form is valid or not.

Let’s change that,

Remove your button, and rather add the following code :

<div *ngIf="myForm.form.valid;then formValid else formError"></div>
<ng-template #formValid><button class="btn btn-primary" type="submit">Submit</button></ng-template>
<ng-template #formError><button class="btn btn-primary" type="button" (click)="submitInvalidForm()">Submit</button></ng-template>

Here we have used the new *ngIf else functionality of Angular 4, depending upon if our form is valid or not, we render our button. So if the form is valid, the button submits the form, and if the form is not valid, we call submitInvalidForm() method. Now, let’s create that method.

submitInvalidForm() {
  if (!this.myForm) { return; }
  const form = this.myForm.form;

  for (const field in this.formErrors) {
    this.formErrors[field] = '';
    const control = form.get(field);

    if (control && !control.valid) {
      const messages = AppValidationMessages.errorMessages[field];
      for (const key in control.errors) {
        this.formErrors[field] = messages[key];
      }
    }
  }
}

As you can see, it’s almost similar to the onValueChanged() method, the only difference is, we add the error message even if the dirty propery is false, or you can say pristine property is true. Again you can refer here to learn more about these properties.

Now, if you submit the form when it’s not valid, it shows the error messages as follows :

Selection_036f

Much better than disabling the button.

For the updated validations, you can refer the same repository, and checkout to the branch named “validations”.

Reference : https://angular.io/guide/form-validation


KNOLDUS-advt-sticker

Advertisements
This entry was posted in Angular Material, AngularJs2.0, JavaScript. Bookmark the permalink.

One Response to Validations for Template-driven forms in Angular 4

  1. Pingback: Validations for Template-driven forms in Angular 4 - Angular Questions

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s