This article will be using the angular directive to create a reusable custom user validation field. A typical use case will be an account lookup field or any user validation field.
I'll be using a bank account validation feature for this document. I came about this idea due to my experience in the fintech industry, where I've had to implement this across various applications, this will allow you to keep your code DRY and it also looks cool 😉.
<input type="text" [appAccountLookup] = "bankInformationForm.value">
First, we create our angular application using the Angular CLI
ng new account-validation
Once that's done, we need to navigate into our application and create our directive, it should be added automatically to your declarations array in your app.module.ts file. You can achieve that with the following command.
cd account-validation
ng generate directive accountLookup
Now in our app folder, let's create an interface that will help define the signature of the object our directive will accept as an input. It should look like this.
**bankInfo.ts**
export class IBankInfo {
bankCode: string;
bankAccountNumber: string;
};
Our newly created directive should have the structure displayed below
** account-lookup.directive.ts **
import { Directive } from '@angular/core';
@Directive({
selector: '[appAccountLookup]'
})
export class AccountLookupDirective {
constructor() {}
}
Before we continue with our directive, let's create the form that will house the input that will use the directive. It should look like this once you are done.
**app.component.ts**
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
bankInformationForm!: FormGroup;
constructor (private formBuilder: FormBuilder) {}
ngOnInit(): void {
this.initializeForm();
}
private initializeForm():void {
this.bankInformationForm = this.formBuilder.group({
bankCode: ["", Validators.required],
bankAccountNumber: ["", Validators.required]
});
}
}
Next up, let's bind our template to the form and use our directive in the account number input field
**app.component.html**
<form [formGroup]= "bankInformationForm">
<fieldset>
<div class="input__field--row">
<label >Bank</label>
<span class="input__wrapper">
<select name="bankCode" id="bankCode" formControlName="bankCode">
<option [disabled]=true value="">Choose Bank</option>
<option value="038">GTBank</option>
</select>
</span>
</div>
<div class="input__field--row">
<label>Account Number</label>
<span class="input__wrapper">
<input type="text" name="bankAccountNumber"id="bankAccountNumber" formControlName="bankAccountNumber" [appAccountLookup] = "bankInformationForm.value"/>
</span>
</div>
</fieldset>
</form>
Now, let's bring in all the elements we need to bring this directive to life.
Our directive will accept the bank details we retrieve from our BankInformation form. We will be making use of reactive forms so don't forget to import the ReactiveFormsModule in your app.module.ts file, we will need to import the Input decorator. We are also going to need the Renderer2 and ElementRef classes to be injected in here to make this work, now your directive should look like this.
It will take the bankDetails object as its Input so we can declare it as it is below. We would also be adding a loading text and a default text to the element that will perform the action.
** account-lookup.directive.ts **
import { Directive, ElementRef, Input, Renderer2} from '@angular/core';
@Directive({
selector: '[appAccountLookup]'
})
export class AccountLookupDirective {
@Input('appAccountLookup') bankDetails!: IBankInfo;
defaultActionText: string = 'Verify Account';
loadingActionText: string = 'Verifying...';
constructor(private renderer: Renderer2, private el: ElementRef) {}
}
Great! next up, let's code the method that will modify our input by adding the necessary elements to it which will be called in the constructor of our AccountLookupDirective class.
private modifyField():void {
// Set style of parent
const parent = this.renderer.parentNode(this.el.nativeElement)
this.renderer.setStyle(parent, 'position', 'relative');
// Create action element inside the input field
const actionButton = this.renderer.createElement('span');
this.renderer.addClass(actionButton, 'inside__input--button');
this.renderer.setProperty(actionButton,'innerHTML',this.defaultActionText);
actionButton.addEventListener('click', (event:any) => {
// Method that will return the account name
});
this.renderer.appendChild(parent, actionButton);
};
In the snippet above we've created the "Verify" action, we also gave it a class of "inside__input - button", the CSS class will be styled like this. Let's also add the class our account name will be displayed, "result__under - text".
.inside__input--button {
position: absolute;
font-size: 10px;
right: 13px;
top:30%;
cursor: pointer;
user-select: none;
}
.result__under--text {
position: absolute;
font-size: 12px;
left: 0px;
bottom: -50%;
cursor: pointer;
user-select: none;
}
What we just did above was to add a verify button inside our input element so our users can click on that button and fetch the account name from the account validation API.
Let's create a utility service that will contain the method which will make the API call to the service to validate the user's bank details.
ng generate service utility
Now let's add the method that will make the API call, your service should look like this.
**utility.service.ts**
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UtilityService {
constructor( private httpClient: HttpClient ) { }
public validateAccount(validateAccount): Observable<any> {
return this.httpClient.post('this.apis.verifyAccount', validateAccount)
}
}
Now import the service in our directive and inject it via the constructor
** app.component.ts **
import { IBankInfo } from './bankInfo';
import { UtilityService } from './utility-service.service';
Now let's code the method that will make this API call and the actions that will be performed after the account name is returned. We will call this method verifyAccountDetails. This is where we retrieve the account name and append it to the input field.
**account-lookup.directive.ts**
private verifyAccountDetails(actionButton: HTMLElement, parent:HTMLElement){
this.renderer.setProperty(actionButton,'innerHTML',this.loadingActionText);
const accountNameDisplay = this.renderer.createElement('span');
this.renderer.addClass(accountNameDisplay, 'result__under--text');
this.renderer.appendChild(parent, accountNameDisplay);
this.utilityService.validateAccount(this.bankDetails)
.subscribe((resp)=> {
actionButton.innerHTML = this.defaultActionText;
this.renderer.setProperty(accountNameDisplay,'innerHTML',resp.accountName);
},
(error: any)=> {
actionButton.innerHTML = this.defaultActionText;
console.log(error);
})
}
In the above snippet, our method accepts the actionButton and the parent element as compulsory parameters. We created the element the returned account name will be displayed and gave a class of result_under - text.
With that, we can easily reuse this directive in any component across our application. You can find the code on my Github, please leave your comments and suggestions.
Top comments (0)