Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Angular Custom Validation Component

5.00/5 (6 votes)
1 May 2018CPOL6 min read 18.9K  
This post is about validation in Angular 2 application, which means it's about validation on client side, not about server side.

Validation is a very important feature of applications which allows data entry (allows to enter data in application) as invalid data can leave application in inconsistent state, invalid date entry crash application and if data is not validated, it can allow injection attacks on application. In a Web application, validation can be done at server side (i.e., on server like IIS, Apache in language C#, PHP, etc.) and at client side (i.e., in browser windows using JavaScript).

This post is about validation in Angular 2 application, which means it's about validation on client side, not about server side. Angular framework allows to do validation in the following two ways:

  1. Template Driven Form
  2. Reactive Form

One of these approaches or a mix of both approaches can be used in application for validation. I am not going to discuss it, as it is provided in detail over here: Forms & Validation. But the following post is going to discuss about custom component, which works with both the validation approach provided by the Angular 2 framework and encapsulates validation code in it.

Custom Validation Component

Let's first create a validation component and make use of it in the application. For a fresh start of application, follow this post: My first Angular 2 app - tutorial. After creating application, create shared module in application by running the below command in integrated terminal of Visual Studio Code.

ng g module shared.

Shared module is where custom validation component will reside, as custom validation component is dumb and it's going to be used by multiple features in application. Run command on integrated terminal creates...

ng g component shared/custom-input

...custom validation component.

custom-input.component.html

HTML
<div class="form-group" >
 <label class="control-label">{{label}}</label>
    <ng-content></ng-content>
    <ng-container *ngIf='isError'>
      <span class="text-danger" *ngFor="let msg of errorMessages"> {{msg}}</span>
    </ng-container>
  </div>
</div> 

Above is the modification done in component HTML, some of the points to note in HTML are:

  1. ng-content - which is a very important tag in this HTML and important for validation component. This Angular 2 Framework tag allows us to push any content in HTML when component is loaded. For example:
    HTML
    <custom-input>
        <input inputRef class="form-control" type="text" />
    </custom-input>

    When the above code is written in component, it will become like this:

    Image 1

    So input element is put inside ng-content when component loaded in HTML page.

  2. inputRef its directive associated with input element, that in turn allows to access input element in component TypeScript (more discussed below) 
  3. When custom component is rendered in browser, it looks as below:

    Image 2

So, HTML tags in component will create structure above as shown in the image below:

Image 3

The image above explains all the tags, which made up component template. Just to note few points:

  1. Border around the text box controls comes when text box becomes invalid. Border to textbox gets applied by 'has-error' class, which gets applied isError becomes true (in which case, it becomes true described by code in typescript file below),
  2. "First Name Required"- this message comes when there is an error. For example, in this image, it is error related to required validation. This is passed as input to component described below.
  3. label,(First Name) - will come from typescript associated with component template. This is passed as input to component described below.

custom-input.component.ts

JavaScript
@Component({
  selector: 'custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.css']
})
export class CustomInputComponent implements OnInit {
  @Input() label: string;
  @Input() validations:  { [index: string]: string};
  @Input() info: string;
 
  @ContentChild(InputRefDirective) input: InputRefDirective;
 
  get isError() {
    return this.input.hasError;
  }
 
   get errorMessages() {
    const errors = this.input.errors;
    const messages = [];
    const keys = Object.keys(this.validations);
 
    keys.forEach(key => {
        if (errors[key]) {
          messages.push(this.validations[key]);
        }
      });
    return messages;
  }
 
  ngOnInit() { }
 
  constructor() { }
}

Above is Component Typescript file. Code in file handles all the backend logic to control Component invalid/valid state, putting red border around control when there is error and displaying error message when control is in invalid state.

Type Name Description
Input    
  label Display label text for control, example, First Name in the above image
  validations Its indexed object holds validations associated with input tag of component
E.x. {'required' : 'First Name required', 'minlength': 'Minimum length is 3 char'}
Note: indexed property name must need to match with type of validation, example, 'required' key match with required validation 
  info WIP (Work in progress)
variable    
  input This component variable allows to get access to input control (which replaces ng-content in template of component) associated with component, for example, the above image in this case 'input textbox'.
Properties    
  isError Component property which finds input element (with help of input directive) and get element has error or not, which in turn helps to put red border around input control
  errorMessages Component property which accesses errors associated input element (with help of input directive) and with the help of input property validations, object find proper error messages and return to display all.

The important thing to note here is InputRefDirective, which accesses input control. Below is code for input-ref directive.

input-ref.directive.ts (Created by ng g directive directive/InputRef)

JavaScript
import { Directive } from '@angular/core';
import { NgControl } from '@angular/forms';
 
@Directive({
  selector: '[inputRef]'
})
export class InputRefDirective {
 
  constructor(private formControl: NgControl) {
  }
 
  get hasError() {
    return this.formControl.dirty && this.formControl.invalid;
  }
 
  get errors() {
    if (this.hasError && this.formControl.errors) {
      return this.formControl.errors;
    }
    return '';
  }
}

Important part of input-ref directive is, with the help of constructor injection (by using angular framework injection), NgControl gets injected in the input-ref directive. By doing that, directive class gets access to input control.

Type Name Description
Properties    
  hasError Property returns true (when control is dirty and invalid state) or false, to indicate there is an error or not
  errors Property returns list of errors associated with input control

So it's end of creation of validation component and its related directive. Now let's see how to use it in application.

app.component.html

Reactive Form

HTML
<form class="needs-validation" [formGroup]="detailForm" novalidate>
    <custom-input [label]="'First Name'" [validations]="{required:'First Name required'}">
      <input inputRef class="form-control" formControlName="firstName" type="text" />
    </custom-input>
    <custom-input [label]="'Last Name'" [validations]="{required:'Last Name required'}">
      <input inputRef class="form-control" formControlName="lastName" type="text" />
    </custom-input>
    <div>
      <button [disabled]="detailForm.invalid" type="button" class="btn btn-primary">Primary</button>
    </div>
</form>

Component.ts for Reactive Form Only

The code below creates form reactive way. The point to note here is that there is not much code to access control and display error (discussed below in advantages).

JavaScript
export class AppComponent implements OnInit {
  title = 'app';
  detailForm: FormGroup;
 
  constructor(private fb: FormBuilder) {
    this.createForm();
  }
 
  ngOnInit() {
  }
 
  private createForm() {
    this.detailForm = this.fb.group({
      firstName: new FormControl('', [Validators.required]),
      lastName: new FormControl('', [Validators.required]),
    });
  }
}

Template Driven Form

HTML
<form class="needs-validation" #detailForm="ngForm" novalidate>
    <custom-input [label]="'First Name'" [validations]="{required:'First Name required'}">
      <input inputRef class="form-control" [(ngModel)]="firstName" name="firstName" type="text" />
    </custom-input>
    <custom-input [label]="'Last Name'" [validations]="{required:'Last Name required'}">
      <input inputRef class="form-control" [(ngModel)]="lastName" name="lastName" type="text" />
    </custom-input>
    <div>
      <button [disabled]="detailForm.invalid" 
      type="button" class="btn btn-primary">Primary</button>
    </div>
</form>

It's not full template of application component code, but it's part of template which uses Custom validation component in both ways, Reactive Form and Template Driven Form way. So not much change in both ways, except change forced by reactive form approach and template driven form approach.

Not much in app.component template to discuss as already discussed before in post. app.component template makes use of custom validation component and passes all required input values (label, info and validation error).

Below is the image of final form after making all changes:

Image 4

Advantage of Using Custom Validation Component

  1. Less code in component template,
    Template driven from:
    HTML
    <label class="control-label">First Name</label>
      <input
      type="text"
      name="firstName"
      [(ngModel)]="firstName"
      minlength="2"
      required>
     
    <div *ngIf="firstName.errors?.required && firstName.touched && firstName.touched"
    class="label label-danger">
      Name is required
    </div>
    <div *ngIf="firstName.errors?.minlength && firstName.touched && firstName.touched"
    class="label label-danger">
      Minimum of 2 characters
    </div>

    Reactive from

    JavaScript
    component.ts
     
     this.detailForm = this.form.group({
           firstName: [null, Validators.compose([Validators.required, Validators.minLength(2)])],     
        });
     
    component.html
    <label class="control-label">First Name</label>
    <input
      type="text"
      name="firstName"
      [(ngModel)]="firstName"
      minlength="2"
      required>
     
    <div *ngIf="detailForm.get('firstName').errors?.required && 
          detailForm.get('firstName').touched && detailForm.get('firstName').touched"
    class="label label-danger">
      Name is required
    </div>
    <div *ngIf="detailForm.get('firstName').errors?.minlength && 
          detailForm.get('firstName').touched && detailForm.get('firstName').touched"
    class="label label-danger">
      Minimum of 2 characters
    </div>

    If there is no custom validation component, in both ways (Template driven or Reactive way), one needs to write down separate div for displaying each error input control has. For example, div for required error and div for minlength.

    But with custom validation control, there is just need of passing validations in validations-property of control and it will take care of it.

    HTML
    <custom-input [label]="'First Name'"
               [validations]="{required:'First Name required', 'minlength':'Minimum of 2 characters'}">
      <input inputRef class="form-control" [(ngModel)]="firstName" name="firstName" type="text" />
    </custom-input>

    Even there is no need to write extra label tag for displaying label for input control. This is also taken care by custom validation control label-property.

  2. Second major difference is, custom validation control encapsulates code of validation in one place only for full application. That means there is no need to write validation code in template in each and every data entry form.

Wrapping Up

Custom validation control is easy and simple to use. It makes use of some advanced stuff like ng-content, directive to get input control under the hood and encapsulate thing in it. Once control is included in application, it reduces the amount of validation code & effort to write validation code in every data entry form.

Full source : Download source (GitHub)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)