Creating a Custom Directive in Angular

Angular directives are one of the framework’s most powerful features. They allow developers to manipulate the DOM, add reusable behaviors, and enhance templates without cluttering component logic. While Angular provides many built-in directives like *ngIf, *ngFor, and [ngStyle], creating custom directives allows you to extend HTML with your own functionality.

This article provides a comprehensive guide to creating custom directives, including examples, best practices, and real-world use cases.

What is a Directive?

In Angular, a directive is a class that can modify the behavior or appearance of DOM elements. There are three types:

  1. Component Directives – Directives with a template (essentially Angular components).
  2. Attribute Directives – Change the appearance or behavior of an element.
  3. Structural Directives – Change the DOM structure (like *ngIf and *ngFor).

In this guide, we focus on attribute directives, which allow you to add reusable behavior to elements.


Why Create a Custom Directive?

Custom directives are useful when you want to:

  • Apply reusable styles or animations.
  • Handle DOM events consistently.
  • Add conditional behaviors across multiple components.
  • Avoid repeating the same logic in multiple components.

Step 1: Generating a Directive Using Angular CLI

Angular CLI makes it easy to generate a directive:

ng generate directive highlight

This command generates:

src/app/highlight.directive.ts

With a basic scaffold:

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

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  constructor() { }
}

Explanation:

  • @Directive decorator marks the class as a directive.
  • selector: '[appHighlight]' allows the directive to be used as an attribute in templates.

Step 2: Implementing an Attribute Directive

To create a directive that changes the background color of an element:

import { Directive, ElementRef, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() set appHighlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
} constructor(private el: ElementRef) {} }

Explanation:

  • ElementRef gives direct access to the host DOM element.
  • @Input() allows passing a value from the template.
  • Using a setter for appHighlight applies the color dynamically when the input changes.

Step 3: Using the Custom Directive in Templates

<p [appHighlight]="'yellow'">This text is highlighted in yellow!</p>
<p [appHighlight]="'lightblue'">This text is highlighted in light blue!</p>

Explanation:

  • [appHighlight]="'yellow'" binds the appHighlight input to the directive.
  • The directive sets the background color dynamically.

Step 4: Adding Dynamic Behavior

You can make the directive respond to events, like mouse hover:

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() appHighlight: string = 'yellow';

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight);
} @HostListener('mouseleave') onMouseLeave() {
this.highlight('');
} private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
} }

Explanation:

  • @HostListener('mouseenter') listens to mouse enter events on the host element.
  • @HostListener('mouseleave') listens to mouse leave events.
  • The directive changes the background color dynamically when the mouse hovers over the element.

Step 5: Using the Directive with Events

<p [appHighlight]="'lightgreen'">Hover over me to see the highlight!</p>
<p [appHighlight]="'pink'">Hover over me too!</p>

Behavior:

  • When the user hovers over the first paragraph, the background turns light green.
  • When the user moves the mouse away, the background resets.
  • Similarly for the second paragraph with pink color.

Step 6: Applying the Directive to Multiple Elements

Custom directives can be reused across multiple elements:

<div [appHighlight]="'orange'">Orange highlight</div>
<span [appHighlight]="'lightcoral'">Light coral highlight</span>
<button [appHighlight]="'lightblue'">Light blue highlight</button>

Explanation:

  • The directive works with any HTML element.
  • Reusability avoids duplicating DOM manipulation logic across components.

Step 7: Combining Inputs with Multiple Features

You can add more inputs to make the directive more flexible, such as text color, font size, or border style:

import { Directive, ElementRef, Input, HostListener } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() appHighlight: string = 'yellow';
  @Input() textColor: string = 'black';
  @Input() fontSize: string = '16px';

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseEnter() {
this.applyStyles(this.appHighlight, this.textColor, this.fontSize);
} @HostListener('mouseleave') onMouseLeave() {
this.applyStyles('', '', '');
} private applyStyles(bgColor: string, color: string, fontSize: string) {
this.el.nativeElement.style.backgroundColor = bgColor;
this.el.nativeElement.style.color = color;
this.el.nativeElement.style.fontSize = fontSize;
} }

Using the Enhanced Directive:

<p [appHighlight]="'lightyellow'" [textColor]="'blue'" [fontSize]="'20px'">
  Hover to see styles applied!
</p>

Explanation:

  • The directive now handles multiple style changes.
  • Developers can customize the background, text color, and font size for each element.

Step 8: Directive Best Practices

  1. Keep Directives Lightweight – Focus on a single responsibility.
  2. Use @HostListener and @HostBinding – Avoid manipulating the DOM directly with ElementRef when possible.
  3. Avoid Heavy Logic in the Directive – Delegate complex business logic to services.
  4. Reusable Across Components – Make the directive configurable using inputs.
  5. Use Renderer2 for Better Security – Instead of manipulating nativeElement directly:
import { Directive, Renderer2, ElementRef, Input, HostListener } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() appHighlight: string = 'yellow';

  constructor(private el: ElementRef, private renderer: Renderer2) {}

  @HostListener('mouseenter') onMouseEnter() {
this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', this.appHighlight);
} @HostListener('mouseleave') onMouseLeave() {
this.renderer.removeStyle(this.el.nativeElement, 'backgroundColor');
} }

Benefits:

  • Avoids direct DOM access, making the directive compatible with server-side rendering and web workers.

Step 9: Using Structural Directives (Optional Advanced Step)

Structural directives modify the DOM structure, e.g., *ngIf or *ngFor. You can create custom structural directives using TemplateRef and ViewContainerRef.

Example (optional): Show/hide element conditionally:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appShowIf]'
})
export class ShowIfDirective {
  constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) {}

  @Input() set appShowIf(condition: boolean) {
this.viewContainer.clear();
if (condition) {
  this.viewContainer.createEmbeddedView(this.templateRef);
}
} }

Usage:

<p *appShowIf="isVisible">This text is visible only if isVisible is true.</p>

Explanation:

  • TemplateRef accesses the template of the host element.
  • ViewContainerRef controls where the template is rendered.
  • The directive conditionally renders elements based on the input.

Summary: Custom Directive Patterns

Basic Attribute Directive

@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  @Input() set appHighlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
} constructor(private el: ElementRef) {} }

Attribute Directive with Hover Effect

@HostListener('mouseenter') onMouseEnter() { ... }
@HostListener('mouseleave') onMouseLeave() { ... }

Enhanced Directive with Multiple Inputs

@Input() textColor: string;
@Input() fontSize: string;

Using Renderer2 for Safety

this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', this.appHighlight);

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *