Custom Validator In Template Driven Forms In Angular

Custom Validator In Template Driven Forms In Angular

In this article, We will learn how to create a custom validator for template-driven forms. To create Angular Forms, we have two APIs in Angular. There are two types of forms: reactive forms and template-driven forms. Validation rules are defined as an HTML attribute in the HTML markup in template-driven forms. Angular comes with a few built-in Validation properties by default. In this article, we'll look at how to use Angular Directive to create a custom validation attribute. We also show you how to inject service into the validation attribute and pass a parameter.

Custom Validator in Template Driven Forms

Create a new Angular project from scratch, Open the app.component.ts file and add the following code.

import { Component } from '@angular/core';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
 
  constructor() {
  }
 
}

Now, Add the following code under the app.component.html file.
<h1>Custom Validator in Template driven forms</h1>
 
<h2>Template Form</h2>
 
<form myform="" ngsubmit="" novalidate="" onsubmit="">
 
  <label>Number :</label>
  <input name="numVal" ngmodel="" numval="ngModel" type="text" /> 
 
  <p>Is Form Valid : {{myForm.valid}} </p>
 
  <p>Form  : {{ myForm.value | json}} </p>
  <p>
    <button disabled="" myform.valid="" type="submit">Submit</button>
  </p> 
 
</form>

It just has a single input field, numVal. Let's make a validator to check that the numVal value is bigger than 10.

Built-in Validators

There are a few validators integrated into the Angular Forms Module. Here's a list of them: However, there is no validator that is greater than.
  1. Required validator
  2. Min length Validator
  3. Max length Validator
  4. Pattern Validator
  5. Email Validator

How to Build Custom Validator in template-driven form

In template-driven forms, creating a Validator is identical to creating an Angular directive. The Validator interface must be implemented by the directive.

Validator Interface

interface Validator {
  validate(control: AbstractControl): ValidationErrors | null
  registerOnValidatorChange(fn: () => void)?: void
}

The validate function must be implemented in the directive. It's worth noting that the validate function and the ValidatorFn Interface have the same signature. Angular looks for and calls the validate method whenever the Validator directive is used.

Validate Function

A Validator is simply a function that must adhere to the ValidatorFn Interface.
interface ValidatorFn {
  (control: AbstractControl): ValidationErrors | null
}

The AbstractControl is sent to the function. FormControl, FormGroup, and FormArray are all derived from this basic class. If the validation was successful, the validator function must return a list of errors, i.e. ValidationErrors, or null if the validation was unsuccessful.

Custom Validator Example

Add the following code to the gte.validator.ts file.
import { Validator, NG_VALIDATORS, FormControl } from '@angular/forms'
import { Directive, OnInit, forwardRef } from '@angular/core';
 
 
@Directive({
  selector: '[gteValidator]',
  providers: [
    { provide: NG_VALIDATORS, useExisting: gteValidatorDirective, multi: true }
  ]
})
export class gteValidatorDirective implements Validator, OnInit {
 
  ngOnInit() {
  }
 
  validate(c: FormControl) {
 
    let v: number = +c.value;
 
    if (isNaN(v)) {
      return { 'gte': true, 'requiredValue': 10 }
    }
 
    if (v <= +10) {
      return { 'gte': true, 'requiredValue': 10 }
    }
 
    return null;
  }
}

The @Directive decorator is used to decorate the directive.

In the HTML template, the directive is used as an attribute. A name or selector is required for the attribute. In the selector metadata part of the directive decorator, we name it gteValidator.
selector: '[gteValidator]',

The Validation capabilities of our directive are unknown to Angular. As a result, we must use the specific injection token NG VALIDATORS to register it in the Angular Providers metadata. We also put multi:true to account for the possibility of additional validation directives.
{ provide: NG_VALIDATORS, useExisting: gteValidatorDirective, multi: true }

The validate method must be implemented by the directive class. The ValidatorFn Interface must be respected by the validate method.
validate(c: FormControl) {

  let v: number = +c.value;

  if (isNaN(v)) {
    return { 'gte': true, 'requiredValue': 10 }
  }

  if (v <= +10) {
    return { 'gte': true, 'requiredValue': 10 }
  }

  return null;
}

It's a straightforward function that determines whether the value is a number less than ten. If it passes all of the tests, it returns null.

ValidationErrors are returned if Validation fails. It's a [key: string]: any key-value pair object that defines the broken rule. The string is the key, and it should include the name of the breached rule. The value can be anything, but it is usually true.

When the validation fails, we return the key-value pair below.
return { 'gte': true, 'requiredValue': 10 }

The validation has failed, as indicated by the 'gte': true: value. The template uses'requiredValue': 10 to show that the anticipated value is greater than 10.

Using the Custom Validator

We don't need to do anything in the component class because this is a template-driven form. Simply add the property gteValidator to the HTML template as shown below.
<label>Number :</label>

<input gtevalidator="" name="numVal" ngmodel="" numval="ngModel" type="text" /> 

<div ngif="!numVal.valid && (numVal.dirty ||numVal.touched)">
  <div ngif="numVal.errors.gte">
    The number should be greater than {{numVal.errors.requiredValue}}
  </div>
</div>

Validators produce ValidationErrors as a result of their work. They are included in the control's collection of errors. The control's valid property is set to false.

As a result, we check to see if the property is valid. We also inspect the property that has been dirty or touched. Because we do not want the error notice to appear when the form is first displayed.

The error message is displayed if the gte is true. It's worth noting that gte is the name of the key we used to build the validator.

RequiredValue is also used to display a meaningful message to the user.
<div ngif="numVal.errors.gte">
  The number should be greater than {{numVal.errors.requiredValue}}
</div>

Passing Parameter to Validator

In the above example, the value of ten has been hardcoded. This will make it impossible to reuse our validator. We must supply the number to be checked as a parameter if we wish to reuse it.

We can use the Input decorator to give the parameter to the Validator because they are directives.

Add the special attribute gteNum="20" to the template.
<input gtenum="20" gtevalidator="" name="numVal" ngmodel="" numval="ngModel" type="text" /> 

The Input decorator can be used to read the gteNum from the template, as illustrated below.
@Input("gteNum") gteNum:number

You can now remove the hardcoded value 10 and replace it with gteNum.

The validator code in its entirety is presented below.
import { Validator, NG_VALIDATORS, FormControl } from '@angular/forms'
import { Directive,  Input } from '@angular/core';
 
@Directive({
  selector: '[gteValidator]',
  providers: [
    { provide: NG_VALIDATORS, useExisting: gteValidatorDirective, multi: true }
  ]
})
export class gteValidatorDirective implements Validator {
 
  @Input("gteNum") gteNum:number
 
  validate(c: FormControl) {
 
    let v: number = +c.value;
 
    if (isNaN(v)) {
      return { 'gte': true, 'requiredValue': this.gteNum }
    }
 
    if (v <= +this.gteNum) {
      return { 'gte': true, 'requiredValue': this.gteNum }
    }
 
    return null;
  }
 
}

Injecting Service into Validator

To validate the value, the validator may rely on an external service. It may, for example, require data from the back-end server.

Let's relocate the aforementioned validator's validation function to a different service. Create a service called gte.service.ts and paste the code below into it.
import { Injectable } from '@angular/core';
 
@Injectable({
  providedIn: 'root',
})
export class gteService {
 
  gte(num:any, requiredValue:Number) : Boolean {
 
    if (isNaN(num)) {
      return false;
    }      
  
    if (num <= +requiredValue) {
      return false;
    }
 
    return true;
  }
}

Create a constructor method in the validation directive and inject the service. The entire code is displayed below.
import { Validator, NG_VALIDATORS, FormControl } from '@angular/forms'
import { Directive,  Input } from '@angular/core';
import { gteService } from 'projects/injectService1/src/app/gte.service';
 
@Directive({
  selector: '[gteValidator]',
  providers: [
    { provide: NG_VALIDATORS, useExisting: gteValidatorDirective, multi: true }
  ]
})
export class gteValidatorDirective implements Validator {
 
  @Input("gteNum") gteNum:number
 
  constructor(private gteService:gteService) {
  }
 
  validate(c: FormControl) {
 
    let v: number = +c.value;
 
    if (this.gteService.gte(v,this.gteNum)) {
      return { 'gte': true, 'requiredValue': this.gteNum }
    }
 
    return null;
  }
}

Conclusion

In template-driven forms, we learned how to establish a custom validator. We also discussed how to inject service and pass parameters into our directive.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in the comment section.

Post a Comment

Previous Post Next Post