DEV Community

loading...

Angular directives: Interesting ways to use it.

Rahman Adewale Hafeez
I'm a web developer loves to build solutions that solves problems, I currently serve as the Team Lead of the front end and design team at Global Accelerex.
・4 min read

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.

gif for validate account

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">
Enter fullscreen mode Exit fullscreen mode

First, we create our angular application using the Angular CLI

ng new account-validation
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
};

Enter fullscreen mode Exit fullscreen mode

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() {}
}
Enter fullscreen mode Exit fullscreen mode

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]
  });
 }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)                                                    {}
}
Enter fullscreen mode Exit fullscreen mode

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);
  };
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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);
  })
 }
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)