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:
- Component Directives – Directives with a template (essentially Angular components).
- Attribute Directives – Change the appearance or behavior of an element.
- 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 theappHighlight
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
- Keep Directives Lightweight – Focus on a single responsibility.
- Use
@HostListener
and@HostBinding
– Avoid manipulating the DOM directly withElementRef
when possible. - Avoid Heavy Logic in the Directive – Delegate complex business logic to services.
- Reusable Across Components – Make the directive configurable using inputs.
- 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);
Leave a Reply