Directive Composition API (v15)

📚 What is Directive Composition API?

hostDirectives lets you compose directives together, applying multiple behaviors:

  • Code reuse: Combine existing directive behaviors
  • No inheritance: Composition over inheritance
  • Expose inputs/outputs: Selectively expose directive APIs
  • Works on components: Components can have host directives too

🎯 Interview Questions

  • Q1: What is the Directive Composition API?
  • A: Feature that allows applying multiple directives to a host element using hostDirectives property, enabling composition without inheritance.
  • Q2: How do you expose host directive inputs/outputs?
  • A: In hostDirectives array, specify inputs: ['inputName'] and outputs: ['outputName'] to expose them on the host.
  • Q3: Can you use host directives on components?
  • A: Yes! Components can use hostDirectives just like directives, allowing behavior composition.
  • Q4: What's the execution order of host directives?
  • A: Host directives are instantiated before the host class, in the order specified in the array.

🔧 Live Demo - Individual Directives

👆 Hover me - I have appHighlightable directive (light blue)
👆 Hover & Click me - I have BOTH directives applied manually

🔧 Live Demo - Composed Directive

👆 Hover & Click me - I use appInteractiveCard which COMPOSES both behaviors!
👆 Another card with different highlight color

The appInteractiveCard directive uses hostDirectives to combine HighlightableDirective and ClickableDirective!

💻 Directive Composition Code


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

// ===== Base directives to compose =====

@Directive({ selector: '[appHighlightable]' })
export class HighlightableDirective {
  highlightColor = input('yellow');
  
  @HostListener('mouseenter') onEnter() {
    this.el.nativeElement.style.backgroundColor = this.highlightColor();
  }
  
  @HostListener('mouseleave') onLeave() {
    this.el.nativeElement.style.backgroundColor = '';
  }
}

@Directive({ selector: '[appClickable]' })
export class ClickableDirective {
  @HostListener('click') onClick() {
    // Click effect
  }
}

// ===== Composed directive using hostDirectives =====

@Directive({
  selector: '[appInteractiveCard]',
  hostDirectives: [
    {
      directive: HighlightableDirective,
      inputs: ['highlightColor'], // Expose this input
    },
    ClickableDirective, // No need to expose anything
  ],
})
export class InteractiveCardDirective {
  // All the behaviors are composed automatically!
}

// ===== Using on a component =====

@Component({
  selector: 'app-button',
  hostDirectives: [
    {
      directive: TooltipDirective,
      inputs: ['appTooltip: tooltip'], // Alias: appTooltip -> tooltip
    },
    {
      directive: DisabledDirective,
      inputs: ['disabled'],
      outputs: ['disabledChange'],
    },
  ],
  template: `<ng-content />`,
})
export class ButtonComponent {
  // Component now has tooltip and disabled behaviors!
}

// Usage:
// <app-button tooltip="Click me!" [disabled]="false">
//   Save
// </app-button>