Skip to main content

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 classes
  • ngStyle - adds and removes inline styles
  • ngModel - 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:

html
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}">
This div will have different classes applied based on component properties.
</div>

In your component:

typescript
export class MyComponent {
isActive = true;
isDisabled = false;

toggleActive() {
this.isActive = !this.isActive;
}
}

NgStyle

ngStyle lets you set inline styles dynamically:

html
<div [ngStyle]="{'color': textColor, 'font-size.px': fontSize}">
This text will have dynamic styling.
</div>

In your component:

typescript
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:

html
<input [(ngModel)]="name" placeholder="Enter your name">
<p>Hello, {{name}}!</p>

Remember to import FormsModule in your module:

typescript
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:

bash
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:

typescript
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:

  1. We use the @Directive decorator to define a directive with a selector [appHighlight].
  2. We inject ElementRef to get access to the DOM element the directive is applied to.
  3. We use @HostListener to listen for the mouseenter and mouseleave events.
  4. We define an @Input property to accept a color from the parent component.
  5. 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:

html
<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:

typescript
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:

html
<button appTooltip="This is a helpful tooltip!">Hover me</button>

Example 2: Debounce Click Directive

Create a directive to prevent multiple rapid clicks:

typescript
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:

html
<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:

typescript
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, and ngModel
  • 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

  1. Create a custom attribute directive that changes text color based on the value of a number (green for positive, red for negative).
  2. Build a "long press" directive that emits an event when a user holds down a button for a specific duration.
  3. Create a directive that validates an input field and applies different styles based on whether the content is valid or not.
  4. 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! :)