Angular Directives

Custom Angular directives development for automated DOM manipulation and reusable behavior integration

Angular Directives is a community skill for building custom Angular directives, covering structural directives, attribute directives, host listeners, renderer patterns, and directive composition for extending element behavior in templates.

What Is This?

Overview

Angular Directives provides patterns for creating custom directives that extend HTML element behavior. It covers attribute directives that modify element appearance or behavior through DOM manipulation and event handling, structural directives that conditionally add or remove elements from the DOM using TemplateRef and ViewContainerRef, host listener and host binding decorators for responding to element events and binding to element properties, renderer patterns using Renderer2 for platform-safe DOM manipulation, and directive composition using hostDirectives to combine multiple directives on a single element declaratively. The skill enables developers to encapsulate reusable DOM behavior as shareable directives across components.

Who Should Use This

This skill serves Angular developers building reusable behavior extensions for template elements, teams creating directive libraries for consistent interaction patterns, and engineers implementing custom structural directives for advanced template control flow.

Why Use It?

Problems It Solves

Repeated DOM manipulation logic across components leads to duplication and inconsistency. Event handling patterns like click-outside or keyboard shortcuts need reusable encapsulation. Conditional rendering beyond @if requires custom structural directives. Direct DOM access without Renderer2 breaks server-side rendering compatibility.

Core Highlights

Attribute directives encapsulate reusable element behavior as decorators. Structural directives control DOM element presence with template manipulation. Host bindings connect directive logic to element properties and classes. Directive composition combines behaviors without nested wrapper elements.

How to Use It?

Basic Usage

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

// Attribute directive
@Directive({
  selector: '[appTooltip]',
  standalone: true,
})
export class TooltipDirective {
  appTooltip =
    input.required<string>();
  private tip:
    HTMLElement | null = null;

  constructor(
    private el: ElementRef
  ) {}

  @HostListener('mouseenter')
  show() {
    this.tip =
      document.createElement(
        'div');
    this.tip.textContent =
      this.appTooltip();
    this.tip.className =
      'tooltip';
    const rect = this.el
      .nativeElement
      .getBoundingClientRect();
    this.tip.style.top =
      `${rect.top - 30}px`;
    this.tip.style.left =
      `${rect.left}px`;
    this.tip.style.position =
      'fixed';
    document.body.appendChild(
      this.tip);
  }

  @HostListener('mouseleave')
  hide() {
    this.tip?.remove();
    this.tip = null;
  }
}

// Usage: <button
//   [appTooltip]="'Save'"
// >Save</button>

Real-World Examples

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

// Structural directive:
// role-based rendering
@Directive({
  selector: '[appIfRole]',
  standalone: true,
})
export class IfRoleDirective {
  appIfRole =
    input.required<string>();
  private shown = false;

  constructor(
    private tmpl:
      TemplateRef<any>,
    private vc:
      ViewContainerRef,
    private auth: AuthService
  ) {
    effect(() => {
      const role =
        this.appIfRole();
      const has =
        this.auth.hasRole(role);
      if (has && !this.shown) {
        this.vc
          .createEmbeddedView(
            this.tmpl);
        this.shown = true;
      } else if (!has
          && this.shown) {
        this.vc.clear();
        this.shown = false;
      }
    });
  }
}

// Usage:
// <div *appIfRole="'admin'">
//   Admin panel
// </div>

// Directive composition
@Directive({
  selector: '[appButton]',
  standalone: true,
  hostDirectives: [
    { directive:
        TooltipDirective,
      inputs: ['appTooltip'] },
  ],
})
export class
    ButtonDirective {}

Advanced Tips

Use directive composition with hostDirectives to combine multiple behaviors on a single element without wrapper elements. Implement structural directives with microsyntax for natural template usage patterns. Use Renderer2 instead of direct DOM manipulation for server-side rendering compatibility.

When to Use It?

Use Cases

Build a tooltip directive that attaches hover behavior to any element without wrapper components. Create a role-based structural directive that shows content based on user permissions. Implement a click-outside directive for closing dropdowns and modals on external clicks.

Related Topics

Angular directives, structural directives, host bindings, Renderer2, and directive composition.

Important Notes

Requirements

Angular 15 or later for standalone directives and directive composition. TypeScript for typed directive inputs and host bindings. Understanding of Angular template syntax and change detection.

Usage Recommendations

Do: use standalone directives for new projects to avoid NgModule declarations. Prefer Renderer2 over direct DOM access for platform compatibility. Use host bindings for class and style manipulation rather than manual element access.

Don't: create directives that handle too many responsibilities when composition would be cleaner. Use ElementRef.nativeElement directly for DOM changes that should go through Renderer2. Forget to clean up event listeners and DOM elements in ngOnDestroy.

Limitations

Directive composition with hostDirectives does not support structural directives as hosts. Complex structural directives with microsyntax have a steep learning curve. Directives cannot access the host component instance directly without explicit injection.