Skip to content
intermediate Phase 4 · Bindings, Structural Directives, and Events

Custom Attribute Directives

Create directives that change element behavior and appearance.

40m
0 problems
Topic Progress 0%

Creating Attribute Directives

Attribute directives change the appearance or behavior of an element without altering the DOM structure. They are applied as attributes on existing elements.

Basic Directive

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

@Directive({
  selector: '[appHighlight]',
  standalone: true,
})
export class HighlightDirective {
  constructor(private el: ElementRef, private renderer: Renderer2) {
    this.renderer.setStyle(this.el.nativeElement, 'background-color', 'yellow');
  }
}
<p appHighlight>This text has a yellow background</p>

Using @HostListener

@HostListener listens to events on the host element:

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

@Directive({
  selector: '[appTooltip]',
  standalone: true,
})
export class TooltipDirective {
  @HostListener('mouseenter')
  onMouseEnter(): void {
    console.log('Mouse entered');
  }

  @HostListener('mouseleave')
  onMouseLeave(): void {
    console.log('Mouse left');
  }

  @HostListener('click', ['$event'])
  onClick(event: MouseEvent): void {
    console.log('Clicked at:', event.clientX, event.clientY);
  }
}
<button appTooltip>Hover over me</button>

Using @HostBinding

@HostBinding sets properties on the host element:

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

@Directive({
  selector: '[appHighlight]',
  standalone: true,
})
export class HighlightDirective {
  @Input() appHighlight = '';

  @HostBinding('style.backgroundColor')
  get backgroundColor(): string {
    return this.appHighlight || 'yellow';
  }

  @HostBinding('class.highlighted')
  get isHighlighted(): boolean {
    return !!this.appHighlight;
  }
}
<p appHighlight="lightblue">Blue highlight</p>
<p appHighlight>Default yellow highlight</p>

Advanced Directive Patterns

Directives can accept multiple inputs, inject services, and compose with other directives.

Multiple Inputs

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

@Directive({
  selector: '[appResize]',
  standalone: true,
})
export class ResizeDirective {
  @Input() appResize: 'small' | 'medium' | 'large' = 'medium';
  @Input() resizable = true;

  @HostBinding('class.resize-small')
  get isSmall(): boolean { return this.appResize === 'small'; }

  @HostBinding('class.resize-medium')
  get isMedium(): boolean { return this.appResize === 'medium'; }

  @HostBinding('class.resize-large')
  get isLarge(): boolean { return this.appResize === 'large'; }

  @HostBinding('style.cursor')
  get cursor(): string { return this.resizable ? 'resize' : 'default'; }
}
<div appResize="large" [resizable]="true">Resizable content</div>

Injecting Services in Directives

import { Directive, inject } from '@angular/core';
import { NotificationService } from './notification.service';

@Directive({
  selector: '[appConfirm]',
  standalone: true,
})
export class ConfirmDirective {
  private notification = inject(NotificationService);

  @HostListener('click')
  onClick(): void {
    const confirmed = confirm('Are you sure?');
    if (confirmed) {
      this.notification.show('Action confirmed');
    }
  }
}

Directive with Renderer2 (Safe DOM Manipulation)

Always use Renderer2 instead of direct DOM access for server-side rendering compatibility:

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

@Directive({
  selector: '[appLoading]',
  standalone: true,
})
export class LoadingDirective {
  private el = inject(ElementRef);
  private renderer = inject(Renderer2);

  @Input() set appLoading(isLoading: boolean) {
    if (isLoading) {
      this.renderer.addClass(this.el.nativeElement, 'loading');
      this.renderer.setAttribute(this.el.nativeElement, 'aria-busy', 'true');
    } else {
      this.renderer.removeClass(this.el.nativeElement, 'loading');
      this.renderer.removeAttribute(this.el.nativeElement, 'aria-busy');
    }
  }
}
<div [appLoading]="isLoading">Content</div>

Quiz

1. What is the difference between a structural directive and an attribute directive?

Question 1 options

2. How do you listen to events on the host element in a directive?

Question 2 options

3. Why should you use Renderer2 instead of direct DOM manipulation?

Question 3 options

Flashcards

Question

What is an attribute directive?

Answer

A directive that changes the appearance or behavior of an element without altering the DOM structure.

Question

How do you listen to host element events?

Answer

@HostListener('click') — decorator that binds to DOM events on the host element.

Question

Why use Renderer2?

Answer

Provides DOM abstraction that works with SSR and web workers. Never access nativeElement directly.

Revision Notes

Key Takeaways

  • 1. Attribute directives change appearance/behavior without altering DOM structure
  • 2. @HostListener responds to events on the host element
  • 3. @HostBinding sets properties on the host element
  • 4. Use Renderer2 for safe DOM manipulation (SSR-compatible)

Interview Tips

  • • Explain the difference between structural and attribute directives
  • • Be ready to describe @HostListener and @HostBinding
  • • Mention Renderer2 as the safe alternative to direct DOM access

Cheat Sheet

Custom Attribute Directives Cheat Sheet

Basic directive:

@Directive({
  selector: '[appHighlight]',
  standalone: true,
})
export class HighlightDirective {
  @HostListener('mouseenter') onMouseEnter(): void {}
  @HostBinding('style.backgroundColor') get bg(): string { return 'yellow'; }
}

Usage:

<p appHighlight>Highlighted text</p>

Key decorators:

  • @HostListener — listen to host events
  • @HostBinding — set host properties
  • @Input — accept data from the template

Continue Learning