Sitemap

Angular Custom Attribute Directives

4 min readNov 13, 2024

--

video version of the blog

What is Custom Directive?

A custom attribute directive in Angular is a type of directive that can change the appearance, behavior, or other properties of a DOM element.

Problem statement

Create a login form validator that can be dynamic based on the field criteria using a directive.

Understanding the Starter project

I have created an angular project with a login form and reactive forms with the help of Bootstrap. Below is the code for the component and template,

<form [formGroup]="loginFormGroup">
<div class="form-control d-flex flex-column">
<label for="username">Username</label>
<input type="text" formControlName="username" name="username"/>
</div>
<div class="form-control d-flex flex-column">
<label for="password">Password</label>
<input type="password" name="password" formControlName="password"/>
</div>
<div class="text-center p-5">
<input type="button" [disabled]="!loginFormGroup.valid" class="w-100" value="login"/>
</div>
</form>
export class AppComponent {
title = 'custom-attribute-direcetive';
constructor(private fb:FormBuilder){}
loginFormGroup = this.fb.group({
username: new FormControl('',Validators.required),
password: new FormControl('', [Validators.required])
})

Here is how it renders on the screen,

Press enter or click to view image in full size
Starter UI for this blog

Creating custom directive

It’s effortless to create a directive using Angular CLI. Let’s create the directive using the below command

ng generate directive customLoginField

(or)

ng g d customLoginField

This will generate a directive and its spec for testing. Let’s understand the directive generated.

Understanding the generated directive

Press enter or click to view image in full size
@Directive({
selector: '[appCustomLoginField]',
standalone: true
})
export class CustomLoginFieldDirective {
}

‘@Decorative’ annotation defines the class as the directive.

“selector” ensures the correct HTML elements are selected to apply the directive. A selector can be any of the below,

  • Element/Tag
  • HTML attributes like type, class, etc.
  • Custom Tag

I will use the custom tag as ‘appCustomLoginField’.

Updating the appearance of the ‘Input’ box

To get access to the elements that are selected by the directive we will add DI via the constructor,

constructor(private element: ElementRef) { }

At this point we have the access to the element. Now lets update the style of the input boxes. I am using ‘ngAfterViewInit’ lifecycle hook to add styles.

ngAfterViewInit() {
this.element.nativeElement.style.borderRadius = '8px';
this.element.nativeElement.style.border = '0.5px solid grey';
}

After this lets add the custom tags to the targeted input fields.

<input type="text" formControlName="username" name="username" appCustomLoginField/>

I have shown how it is added for the username you can add the same for the password field. After this, both username and password fields should have received the rounded border and border styles that we have configured in the directive.

Get Anbumani Mailathipathy’s stories in your inbox

Join Medium for free to get updates from this writer.

You are free to add more styles, and update styles dynamically using input params.

Validating the field using validator implementation

Lets implement a validator that can handle dynamic changes to the validation criteria,

First, let's implement ‘validator’ to the directive,

class CustomLoginFieldDirective implements Validator

result of this we have to provide the implementation for two methods,

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

the validate method evaluates the input against the validation criteria and returns the error or nothing. Below is the implementation of the validate method.

validate(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if(!value)
return null;

const hasMinLengthError = value.length < this.criteria.minLength;
const hasLowerCaseError = this.criteria.requireLowerCase? !/[a-z]/.test(value):false;
const hasUpperCaseError = this.criteria.requireLowerCase? !/[A-Z]/.test(value):false;
const hasDigitsError = this.criteria.requireLowerCase? !/[0-9]/.test(value):false;
const hasSpecialCharError = this.criteria.requireLowerCase? !/[$#%@]/.test(value):false;

let errors: string[]=[];
if(hasMinLengthError)
errors.push("Min of "+ this.criteria.minLength+" length is required");
if(hasLowerCaseError)
errors.push("Atleast one Lowercase letter is required");
if(hasUpperCaseError)
errors.push("Atleast one Uppercase letter is required");
if(hasDigitsError)
errors.push("Atleast one number [0-9] is required");
if(hasSpecialCharError)
errors.push("Atleast one of [$#%@] letter is required");
if(!errors.length)
return null;
else
return {loginFieldError: errors};

}

Here we are checking the value from the control by matching against the regular expression that matches our criteria. In the end, pushing all the errors into a variable and sending it along the error.

‘registerOnValidatorChange’ method will trigger revalidation when the validator changes. For this method to work we will create a variable and invoke the validation again.

private onChange: ()=> void = ()=>{};


ngOnChanges(changes: SimpleChanges): void {
if(changes['criteria'])
this.onChange();
}
registerOnValidatorChange?(fn: () => void): void {
this.onChange = fn;
}

Attaching the validator to Reactive Form

Our custom validator is ready to tie this with angular forms we have to include into NG_Validators with the help of providers,

providers:[
{
provide: NG_VALIDATORS,
useExisting: CustomLoginFieldDirective,
multi: true
}
],

Here we specify directive is providing a NG_VALIDATOR and validator is the nothing but the directive and multi specifies that multiple validators can be applied on the same form control.

At this point, the validator will work by passing the criteria on the HTML.

<input type="text" formControlName="username" name="username" [appCustomLoginField]="usernameCriteria"/>
<input type="password" name="password" formControlName="password" [appCustomLoginField]="passwordCriteria"/>

Displaying the Errors on the screen

To make the user aware of the error on the form field we can display the error using a for loop on the errors.

 <div class="alert alert-danger" *ngIf="getPasswordErrors() && getPasswordErrors().length" role="alert">
<ul>
<li *ngFor="let error of getPasswordErrors()">{{error}}</li>
</ul>
</div>
getPasswordErrors(){
return this.loginFormGroup.controls['password'].errors?this.loginFormGroup.controls['password'].errors['loginFieldError']:[];
}
Press enter or click to view image in full size
final output

Conclusion

By creating custom directives we can reuse them at any place this is one of the use cases where password validators can be used in many places in the same application. This makes the code clean and more maintainable.

There are many other scenarios you can create your own custom directives to solve our problems. You can find the final code in the below GitHub link.

https://github.com/amaialth/custom-attribute-direcetive

Comment your questions and keep learning!!

--

--

Anbumani Mailathipathy
Anbumani Mailathipathy

Written by Anbumani Mailathipathy

Java Full Stack Developer with Strong Cloud knowledge. Having over 10 years development experience in IT industry.