Dev Study
← 解説「カスタム属性ディレクティブ — 要素に振る舞いを足す」に戻る

サンプルコードで身につける: カスタム属性ディレクティブ — 要素に振る舞いを足す

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1最小のカスタムディレクティブ

ディレクティブの最小構成は、[名前] 形式の selector と、要素を触るための ElementRef だけです。属性を付けるだけで要素の背景が変わる、という「振る舞いの後付け」の核がこの数行に詰まっています。

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

@Directive({
  selector: '[appMarker]', // 属性として付けるので [ ] で囲む
})
export class MarkerDirective {
  private el = inject(ElementRef);

  constructor() {
    // 付けられた要素に蛍光ペン風の背景を敷く
    this.el.nativeElement.style.backgroundColor = 'khaki';
  }
}

2ホバーで背景色を変える

host にマウスイベントを登録し、乗ったときと離れたときで背景色を切り替える定番の例です。ElementRefで要素を触る処理とイベントへの反応を組み合わせると、ディレクティブは一気に実用的になります。

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

@Directive({
  selector: '[appHoverHighlight]',
  // ホスト要素のマウスイベントに反応する
  host: {
    '(mouseenter)': 'onEnter()',
    '(mouseleave)': 'onLeave()',
  },
})
export class HoverHighlightDirective {
  private el = inject(ElementRef);

  onEnter() {
    this.el.nativeElement.style.backgroundColor = 'lightcyan';
  }

  onLeave() {
    this.el.nativeElement.style.backgroundColor = '';
  }
}

3input() でディレクティブに値を渡す

ディレクティブもコンポーネントと同じように input() で値を受け取れます。effect と組み合わせれば、親が渡す色が変わるたびに要素へ反映されます。使う側は <p appTint [appTint]... ではなく [tintColor]="..." のように通常の入力として渡します。

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

@Directive({
  selector: '[appTint]',
})
export class TintDirective {
  private el = inject(ElementRef);

  // ディレクティブも input() で値を受け取れる
  tintColor = input('lightyellow');

  constructor() {
    // 入力が変わるたびに背景へ反映する
    effect(() => {
      this.el.nativeElement.style.backgroundColor = this.tintColor();
    });
  }
}

// 使う側: <p appTint tintColor="mistyrose">ピンクの段落</p>

4使う側は imports に登録して属性で付ける

自作ディレクティブはコンポーネントと同じく、使う側の imports への登録が必要です。1つのディレクティブを複数の要素に付けられるのがコンポーネントとの大きな違いで、同じ振る舞いをあちこちで再利用できます。

import { Component } from '@angular/core';
import { MarkerDirective } from './marker.directive';

@Component({
  selector: 'app-notice-list',
  // ディレクティブも imports に登録して使う
  imports: [MarkerDirective],
  // 同じ振る舞いを複数の要素へ自由に付けられる
  template:
    '<p appMarker>重要なお知らせ</p>' +
    '<p>ふつうのお知らせ</p>' +
    '<li appMarker>締め切りは金曜日</li>',
})
export class NoticeListComponent {}

5表示されたら自動でフォーカスする

画面に現れた入力欄へ自動でカーソルを入れる、実務でよく欲しくなるディレクティブです。描画が終わったあとに一度だけ実行したい処理は afterNextRender に書きます。モーダルを開いた直後の入力欄などに付けると効果的です。

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

@Directive({
  selector: '[appAutofocus]',
})
export class AutofocusDirective {
  private el = inject(ElementRef);

  constructor() {
    // 描画が終わったあとに一度だけ実行される
    afterNextRender(() => {
      this.el.nativeElement.focus();
    });
  }
}

// 使う側: <input appAutofocus placeholder="検索">
// モーダルを開いた直後の入力欄などに付けると便利