Angular Attribute Directives
Introduction
Attribute directives are one of the most powerful features in Angular that allow you to change the appearance or behavior of DOM elements. Unlike structural directives that change the DOM layout by adding or removing elements, attribute directives only change the element they are applied to.
In this tutorial, you'll learn:
- What attribute directives are
- How they differ from structural directives
- How to use built-in attribute directives
- How to create your own custom attribute directives
What are Attribute Directives?
Attribute directives are instructions that tell Angular to change the appearance or behavior of a DOM element. They are applied to elements using the bracket syntax or as normal HTML attributes.
Some common built-in attribute directives in Angular include:
ngClass
- adds and removes CSS classesngStyle
- adds and removes inline stylesngModel
- adds two-way data binding to an HTML form element
Built-in Attribute Directives
Let's explore some of the built-in attribute directives that Angular provides:
NgClass
ngClass
allows you to dynamically add or remove CSS classes:
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}">
This div will have different classes applied based on component properties.
</div>
In your component:
export class MyComponent {
isActive = true;
isDisabled = false;
toggleActive() {
this.isActive = !this.isActive;
}
}
NgStyle
ngStyle
lets you set inline styles dynamically:
<div [ngStyle]="{'color': textColor, 'font-size.px': fontSize}">
This text will have dynamic styling.
</div>
In your component:
export class MyComponent {
textColor = 'blue';
fontSize = 16;
increaseSize() {
this.fontSize += 2;
}
changeColor() {
this.textColor = this.textColor === 'blue' ? 'red' : 'blue';
}
}
NgModel
ngModel
provides two-way data binding for form elements:
<input [(ngModel)]="name" placeholder="Enter your name">
<p>Hello, {{name}}!</p>
Remember to import FormsModule
in your module:
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
FormsModule
// other imports
],
// other module properties
})
export class AppModule { }
Creating Custom Attribute Directives
Now let's create our own custom attribute directive. We'll build a simple highlight directive that changes the background color of an element when the user hovers over it.
Step 1: Generate the directive
Using Angular CLI, you can generate a new directive with:
ng generate directive highlight
This will create a file highlight.directive.ts
with a basic directive structure.
Step 2: Implement the directive
Update the generated file with this code:
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input() appHighlight = '';
@Input() defaultColor = 'yellow';
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight || this.defaultColor);
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight('');
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
Let's break down what's happening:
- We use the
@Directive
decorator to define a directive with a selector[appHighlight]
. - We inject
ElementRef
to get access to the DOM element the directive is applied to. - We use
@HostListener
to listen for themouseenter
andmouseleave
events. - We define an
@Input
property to accept a color from the parent component. - We provide a default color value for when no color is specified.
Step 3: Using the directive
Now you can use the directive in your templates:
<p appHighlight="lightblue">Hover over me to see a lightblue highlight!</p>
<p [appHighlight]="'pink'" [defaultColor]="'lightgreen'">
Hover over me for pink, but I'll use lightgreen if pink is not available.
</p>
<p appHighlight>Hover over me for the default yellow highlight.</p>
Real-world Examples
Let's look at some practical examples of custom attribute directives:
Example 1: Tooltip Directive
Create a directive that shows a tooltip when hovering over an element:
import { Directive, Input, ElementRef, HostListener, Renderer2 } from '@angular/core';
@Directive({
selector: '[appTooltip]'
})
export class TooltipDirective {
@Input('appTooltip') tooltipText = '';
tooltip: HTMLElement | null = null;
constructor(private el: ElementRef, private renderer: Renderer2) { }
@HostListener('mouseenter') onMouseEnter() {
if (!this.tooltip) {
this.showTooltip();
}
}
@HostListener('mouseleave') onMouseLeave() {
if (this.tooltip) {
this.renderer.removeChild(document.body, this.tooltip);
this.tooltip = null;
}
}
private showTooltip() {
this.tooltip = this.renderer.createElement('div');
this.renderer.appendChild(
this.tooltip,
this.renderer.createText(this.tooltipText)
);
// Style the tooltip
this.renderer.setStyle(this.tooltip, 'position', 'absolute');
this.renderer.setStyle(this.tooltip, 'background', 'black');
this.renderer.setStyle(this.tooltip, 'color', 'white');
this.renderer.setStyle(this.tooltip, 'padding', '5px 10px');
this.renderer.setStyle(this.tooltip, 'border-radius', '3px');
this.renderer.setStyle(this.tooltip, 'z-index', '1000');
// Position the tooltip
const hostPos = this.el.nativeElement.getBoundingClientRect();
this.renderer.setStyle(this.tooltip, 'top', `${hostPos.bottom + 5}px`);
this.renderer.setStyle(this.tooltip, 'left', `${hostPos.left}px`);
this.renderer.appendChild(document.body, this.tooltip);
}
}
Use it in your template:
<button appTooltip="This is a helpful tooltip!">Hover me</button>
Example 2: Debounce Click Directive
Create a directive to prevent multiple rapid clicks:
import { Directive, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Directive({
selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit, OnDestroy {
@Input() debounceTime = 500; // default to 500ms
@Output() debounceClick = new EventEmitter();
private clicks = new Subject();
private subscription: Subscription | undefined;
constructor() { }
ngOnInit() {
this.subscription = this.clicks
.pipe(debounceTime(this.debounceTime))
.subscribe(e => this.debounceClick.emit(e));
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
@HostListener('click', ['$event'])
clickEvent(event: any) {
event.preventDefault();
event.stopPropagation();
this.clicks.next(event);
}
}
Use it in your template:
<button
(debounceClick)="saveData()"
[debounceTime]="700"
appDebounceClick
>
Save Data
</button>
Advanced Topics: Event Handling with HostListener
The @HostListener
decorator lets your directives listen to host element events:
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[appTrackClick]'
})
export class TrackClickDirective {
private clickCount = 0;
constructor(private el: ElementRef) { }
@HostListener('click', ['$event'])
onClick(event: Event) {
this.clickCount++;
console.log(`Element clicked ${this.clickCount} times`);
if (this.clickCount === 5) {
alert('You clicked 5 times!');
this.clickCount = 0;
}
}
@HostListener('document:click', ['$event'])
onDocumentClick(event: Event) {
// This listens for clicks anywhere in the document
if (event.target !== this.el.nativeElement) {
console.log('Clicked outside our element');
}
}
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
console.log(`Window resized: ${window.innerWidth}x${window.innerHeight}`);
}
}
Summary
In this tutorial, you learned:
- What attribute directives are and how they change the appearance or behavior of DOM elements
- How to use built-in Angular attribute directives like
ngClass
,ngStyle
, andngModel
- How to create custom attribute directives with
@Directive
- How to handle events in directives using
@HostListener
- How to pass data to directives with
@Input
- Practical examples of directives like tooltips and debounce click handlers
Attribute directives are a powerful way to extend HTML with custom behaviors and are an essential part of any Angular developer's toolkit.
Additional Resources
Exercises
- Create a custom attribute directive that changes text color based on the value of a number (green for positive, red for negative).
- Build a "long press" directive that emits an event when a user holds down a button for a specific duration.
- Create a directive that validates an input field and applies different styles based on whether the content is valid or not.
- Implement a directive that allows elements to be resizable by dragging their edges.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)