Building an IBAN validation directive in Angular

in #angular6 years ago

Users never make mistakes when entering data, or it happens so rarely that we can just ignore it, ... said no developer ever 🙂

Input validation is important, and in Angular we can use some form field validation out-of-the-box; min and max length, required and pattern.

Even though these are already powerful options for data validation, sooner or later we will want to do some more complex validation and build our own validator.

IBAN Validator


In this post, I will show how to make a directive with an IBAN validator. I'm starting from a default angular cli template, find out here how to set it up in Visual Studio.

Important for this is to use the Directive annotation. Here we can specify the selector for our directive. In this case (selector: '[iban][ngModel]') our directive will match to elements with an iban and ngModel attribute.

We will implement the Validator interface, which will provide us with the validate function for our custom validation.

iban-validator.directive.ts

import { Directive, forwardRef, Attribute } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms';

@Directive({
  selector: '[iban][ngModel]',
  providers: [{ provide: NG_VALIDATORS, useExisting: forwardRef(() => IbanValidator), multi: true }]
})
export class IbanValidator implements Validator {

  constructor() {
  }

  validate(control: AbstractControl): { [key: string]: any } {
    let value = control.value;
    if (!value) {
      return null;
    }

    let parsedValue: string = String(value);
    let invalid: boolean = false;

    // iban should be at least 15 characters long
    if (parsedValue.length < 15) {
      return { invalidIban: true };
    }

    // test for illegal characters
    let regexp = new RegExp("^&#91;a-zA-Z0-9&#93;+$");
    if (regexp.test(parsedValue) == false) {
      return { invalidIban: true };
    }

    // move the first four characters to the back
    // and make sure everything is uppercase
    parsedValue = (parsedValue.substr(4) + parsedValue.substr(0, 4))
                  .toUpperCase();
    let valueWithConvertedNumbers: string = "";
    for (var i = 0; i < parsedValue.length; i++) {
      let character: number = parsedValue.charCodeAt(i);

      // If the character is A-Z, we need to
      // convert it to a number from 10 - 35
      if (character > 64 && character < 91) {
        valueWithConvertedNumbers += String(character - 55);
      }
      else {
        valueWithConvertedNumbers += String.fromCharCode(character);
        }
    }

    let modulo = this.modulo(valueWithConvertedNumbers, 97);
    if (modulo !== 1) {
      return { invalidIban: true };
    }

    return null;
  }

  // Modulo of large numbers
  // See https://stackoverflow.com/questions/929910/modulo-in-javascript-large-number
  modulo(divident, divisor): number {
    let partLength = 10;

    while (divident.length > partLength) {
      var part = divident.substring(0, partLength);
      divident = (part % divisor) + divident.substring(partLength);
    }

    return divident % divisor;
  }
}



So what's going on in the validate function?
The first thing you can see that is maybe a bit strange is the use of forwardRef.

In out NG_VALIDATORS we want to provide an instance of our IBAN validator for dependency injection in our application.The problem is that our IbanValidator is not defined yet, but forwardRef makes this possible.

In the validate function itself, we first check the length. Be wary that I only check for the minimum length of the text. If you want, you could extend the code here to check the exact length for each country code. Check the example here for a list of countries + length

After that, I move the first 4 characters to the back of the text, convert all letters to numbers (A=10, B=11,...) and then do MOD 97, if the result is 1 then the IBAN is valid.

App module


All that is left to do now is to add it to our declarations in our module.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { IbanValidator } from './iban-validator.directive';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
    IbanValidator
  ],
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }



Now let's try it out in a simple demo.

First, change app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {

  ibanValue = "";

  handleDataSubmitted(): void {
    alert(this.ibanValue);
  }
}



And also change the html page.

app.component.html

<h1>Iban validator</h1>

<form name="testForm"
      novalidate 
      class="form"
      #testForm="ngForm">
  <div class="form-group">

    <label for="ibanControl"
           class="control-label">Iban</label>

    <input type="text" 
           class="form-control"
           name="ibanControl"
           iban
           [(ngModel)]="ibanValue"    
           #iban="ngModel" />
  </div>

  <p *ngIf="iban?.hasError('invalidIban') && iban?.touched"
     class="alert alert-danger">
    IBAN is invalid
  </p>

  <p>
    <button type="button" 
            class="btn btn-default"
            (click)="submitData()"
         [disabled]="!testForm.valid && testForm.touched">
      Submit
    </button>
  </p>
</form>


Let's see what is important here.

In our form tag, we use 'novalidate', this disables the native validation, so we can use our own custom validation.

We also specify a template variable for our form #testForm, like this we can disable our submit button if the form is not valid with [disabled]="!testForm.valid".

The same with our input control, that we give the template variable #iban. As you can see, we can check for hasError('invalidIban'), which is the return value of our custom validator.

We also check for iban?.touched to make sure validation only happens after the input loses focus and does not fire when the user starts to type the first time.

Now when we put a wrong IBAN number in the input box and focus away, the error message will be shown.

Code example


If you want to see a code example, you can always check out this live demo

Let me know in the comments if the post was useful or if something is not clear.

Thanks for reading!

Coin Marketplace

STEEM 0.20
TRX 0.24
JST 0.038
BTC 97120.80
ETH 3374.08
USDT 1.00
SBD 3.13