angular.io Guide: Attribute Directives

これは、Angular の公式ドキュメントの [Attribute Directives](https://angular.io/guide/attribute-directives) の章 を意訳したものです｡
駆け足で翻訳したので至らない点もありますが、あしからずご承知おきください｡

バージョン 4.2.6 のドキュメントをベースにしています｡

# Attribute Directives
Attribute Directive は、DOM要素の見た目や動作を変更します｡
ここから、Attribute Directive の [example を見たり](https://angular.io/generated/live-examples/attribute-directives/eplnkr.html)、[ダウンロード](https://angular.io/generated/zips/attribute-directives/attribute-directives.zip) ができます｡

## Directive（ディレクティブ）の概要
Angular には、3種類のDirectiveがあります｡
1. Components ... テンプレートの使用したDirective
2. Structural Directives（構造ディレクティブ） ... DOM要素を追加・削除してDOMレイアウトを変更できるDirective
3. Attribute Directive（属性ディレクティブ） ... 要素、コンポーネント、または別のディレクティブの外観や動作を変更できるDirective

Components は3種類のディレクティブの中で、最も一般的に使われます。
[QuickStart Guide](https://angular.io/guide/quickstart) でコンポーネントを初めて見たことがありますよね？

構造ディレクティブは、ビューの構造を変更します。
2つ例を挙げると `NgFor` と `NgIf` です。[Structural Directives](https://angular.io/guide/structural-directives) のガイドでそれらについて学んでください。

属性ディレクティブは、要素の属性として使用されます。例えば [Template Syntax](https://angular.io/guide/template-syntax) ガイドの組み込み `NgStyle` ディレクティブは、同時に複数の要素スタイルを変更することができます。

## シンプルな属性ディレクティブを作ってみよう
attribute directive では、属性を識別するセレクタを指定する `@Directive` でアノテーションが付けられたコントローラクラスを最低限構築する必要があります。
コントローラクラスで必要なディレクティブの振る舞いを実装します。

このページでは、ユーザーがその要素の上をhoverしたときに要素の背景色を設定する、シンプルな `myHighlight` 属性ディレクティブを構築する方法を記載しています。
これは下記のように書くことができます:

*src/app/app.component.html（適用済み）*

```
<p myHighlight>Highlight me!</p>
```

### ディレクティブのコードを書く
[setup](https://angular.io/guide/setup) の手順に沿って、属性ディレクティブという名前の新しいローカルプロジェクトを作成します。

指定されたフォルダに次のソースファイルを作成します。

*src/app/highlight.directive.ts*

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

@Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
    constructor(el: ElementRef) {
       el.nativeElement.style.backgroundColor = 'yellow';
    }
}
```

`import` ステートメントは、Angular コアのシンボルを指定します。

1. `Directive` は `@Directive` デコレータの機能を提供します。
2. `ElementRef` は、コードがDOM要素にアクセスできるように、ディレクティブのコンストラクタに記述します。
3. `Input` で、データがバインディング式からディレクティブに流れるようにします｡

次に、`@Directive` デコレータ関数には、構成オブジェクトのディレクティブメタデータが引数として含まれています。
`@Directive` は、ディレクティブに関連付けられているテンプレート内のHTMLを識別するためにCSSセレクタを必要とします。
属性のCSSセレクタは、角括弧内の属性名です。ここでのディレクティブのセレクタは `[myHighlight]` です。
Angularは、テンプレート内で `myHighlight` という名前の属性を持つすべての要素を検索します。

> ディレクティブ名を `highlight` としないのはなぜですか？
> 
> `highlight` は `myHighlight` よりも簡潔な名前ですし、実際動作もするはずです｡ただし、セレクタ名にプレフィックスを付けて、標準のHTML属性とコンフリクトしないよう、未然に防ぐことをお勧めします。
> 
> また、 `highlight` ディレクティブ名には、ngをつけないようにしてください。`ng` 接頭辞はAngularのために予約されており、使用すると調査が難しいバグが発生する可能性があるからです。
> 今回のようなシンプルなデモでは、短い接頭辞 `my` がカスタムディレクティブとして区別するのに役立ちます。

`@Directive` メタデータの後には、ディレクティブのコントローラクラス（`HighlightDirective` という）があります。このクラスには、ディレクティブのロジックが含まれています。 `HighlightDirective` をエクスポートすると、他のコンポーネントからアクセスできるようになります。

Angular は、一致する要素ごとにディレクティブのコントローラクラスの新しいインスタンスを作成し、コンストラクタにAngular `ElementRef` を注入します。
`ElementRef` は、`nativeElement` プロパティを使用してDOM要素に直接アクセスするサービスです。

## 属性ディレクティブを適用する
新しい `HighlightDirective` を使用するには、ディレクティブを属性として段落（`<p>`）要素に適用するテンプレートを作成します。 Angularの場合、`<p>` 要素は属性ホストです。

テンプレートを次のような `app.component.html` ファイルに入れます。

*src/app/app.component.html*

```
<h1>My First Attribute Directive</h1>
<p myHighlight>Highlight me!</p>
```

今度は、このテンプレートを `AppComponent` で参照してください:

*src/app/app.component.ts*

```
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html'
})
export class AppComponent {
  color: string;
}
```

次に`、import` ステートメントを追加して `Highlight` ディレクティブをフェッチし、そのクラスを宣言の `NgModule` メタデータに追加します。このようにして、Angular はテンプレート内で `myHighlight` を検出したときにディレクティブを認識します。


*src/app/app.module.ts*

```
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { HighlightDirective } from './highlight.directive';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [
    AppComponent,
    HighlightDirective
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }
```

アプリケーションが実行されると、`myHighlight` ディレクティブは段落テキストを強調表示します。
![First Highlight](https://angular.io/generated/images/guide/attribute-directives/first-highlight.png)

> 作ったディレクティブが動かない？
> 
> `@NgModule` の宣言属性にディレクティブを追加することを忘れていませんか？
> 忘れているかどうか確認するのは簡単です！開発者ツールでコンソールを開き、次のようなエラーを調べてください:
> ```
> EXCEPTION: Template parse errors:
>  Can't bind to 'myHighlight' since it isn't a known property of 'p'.
> ```
> 
> Angular は何かにバインドしようとしていますが、このディレクティブはモジュールの `declarations ` 配列で見つけることができません。
> `declarations ` の配列で `HighlightDirective` を指定すると、このモジュールで宣言されたコンポーネントにディレクティブを適用できることがわかります。

まとめると、Angularは `<p>` 要素の `myHighlight` 属性を検出しました。
れは `HighlightDirective` クラスのインスタンスを作成し、`<p>` 要素の背景スタイルを黄色に設定するディレクティブのコンストラクタに `<p>` 要素への参照を inject しました。

## ユーザーが開始したイベントに対応する
現在、`myHighlight` は要素の色を設定しています。このディレクティブはより動的になる可能性があります。これは、ユーザーが要素の中に出入りするときを検出し、ハイライトカラーを設定またはクリアすることによって応答することができます。

*src/app/highlight.directive.ts (imports)*
```
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
```

次に、マウスが入ったり出たりするときに応答する2つのイベントハンドラを追加します。それぞれのイベントハンドラは、`HostListener` デコレータによって装飾されます。

```
@HostListener('mouseenter') onMouseEnter() {
  this.highlight('yellow');
}

@HostListener('mouseleave') onMouseLeave() {
  this.highlight(null);
}

private highlight(color: string) {
  this.el.nativeElement.style.backgroundColor = color;
}
```

`@HostListener` デコレータを使用すると、属性ディレクティブ（この場合は `<p>`）をホストするDOM要素のイベントにサブスクライブできます。

> もちろん、標準のJavaScriptを使用してDOMにアクセスし、イベントリスナーを手動でアタッチすることができます。そのアプローチには少なくとも3つの問題があります。
> 
> 1. リスナーを正しく記述する必要があります。
> 2. メモリーリークを避けるため、ディレクティブが破棄されたときにリスナーを切り離す処理を記述しなければなりません。
> 3. DOM APIに直接アクセスすることは、ベストプラクティスではありません。

ハンドラは、コンストラクタで宣言して初期化するDOM要素の色 `el` を設定するヘルパーメソッドに委譲します。

*src/app/highlight.directive.ts (constructor)*

```
constructor(private el: ElementRef) { }
```

追記されたディレクティブが全部ここにあります:

*src/app/highlight.directive.ts*

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

@Directive({
  selector: '[myHighlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef) { }

  @HostListener('mouseenter') onMouseEnter() {
    this.highlight('yellow');
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
```

アプリを実行して、マウスが `<p>` の上に移動して外に出るときに消えたら、背景色が表示されることを確認します。

![Second Highlight](https://angular.io/generated/images/guide/attribute-directives/highlight-directive-anim.gif)

## *@Input* データバインディングでディレクティブに値を渡す

現在、強調表示の色はディレクティブの記述内でハードコードされています。それは柔軟性がありません。このセクションでは、ディレクティブを適用しながらハイライトカラーを設定する権限を開発者に与える方法をご紹介します。

まず、`highlightColor` プロパティを次のようにディレクティブクラスに追加します。

*src/app/highlight.directive.ts (highlightColor)*

```
@Input() highlightColor: string;
```

### *@Input* プロパティへのバインディング
`@Input` デコレータに注目してください。ディレクティブの `highlightColor` プロパティをバインディングに使用できるように、クラスにメタデータを追加します。

これは、データがバインディング式からディレクティブに流れるため、入力プロパティと呼ばれます。その入力メタデータがなければ、Angularはバインディングを拒否します。詳細は以下を参照してください。

`AppComponent` テンプレートに以下のディレクティブバインディングバリエーションを追加して試してください：

*src/app/app.component.html (excerpt)*

```
<p myHighlight highlightColor="yellow">Highlighted in yellow</p>
<p myHighlight [highlightColor]="'orange'">Highlighted in orange</p>
```

`AppComponent` に `color` プロパティを追加します。

*src/app/app.component.ts (class)*

```
export class AppComponent {
  color = 'yellow';
}
```

それはプロパティバインディングでハイライトの色を制御します。

*src/app/app.component.html (抜粋)*

```
<p myHighlight [highlightColor]="color">Highlighted with parent component's color</p>
```

それは良いことですが、指示文を同時に適用し、このような同じ属性に色を設定するとよいでしょう。

*src/app/app.component.html (color)*

```
<p [myHighlight]="color">Highlight me!</p>
```

`[myHighlight]` 属性バインディングは、強調表示ディレクティブを `<p>` 要素に適用し、ディレクティブのハイライトカラーをプロパティバインディングで設定します。
両方の作業を行うには、ディレクティブの属性セレクタ（`[myHighlight]`）を再利用しています。それは明快でコンパクトな構文です。

ディレクティブの `highlightColor` プロパティの名前を `myHighlight` に変更する必要があります。これは、これが `color` プロパティバインディングの名前になっているためです。

*src/app/highlight.directive.ts (renamed to match directive selector)*

```
@Input() myHighlight: string;
```

これはイヤですね｡`myHighlight` という単語は恐ろしいプロパティ名であり、プロパティの意図を伝えるものではありません。

### @Input エイリアスでバインディング
幸いなことに、ディレクティブプロパティに任意の名前を付けて、バインディングの目的で別名を付けることができます。

元のプロパティ名を復元し、セレクタを `@Input` の引数にエイリアスとして指定します。

*src/app/highlight.directive.ts (color property with alias)*

```
@Input('myHighlight') highlightColor: string;
```

このディレクティブの内部では、プロパティは `highlightColor` として知られています。ディレクティブの外側で、バインドする場所は、`myHighlight` と呼ばれます。

ここで、両者とも都合の良いベストなパターンを教えます！
あなたが望むプロパティ名とバインディング構文: 

*src/app/app.component.html (color)*

```
<p [myHighlight]="color">Highlight me!</p>
```

`highlightColor` にバインドするので、`onMouseEnter()` メソッドを変更して使用してください。誰かが `highlightColor` にバインドするのを怠った場合は、赤で強調表示します：

*src/app/highlight.directive.ts (mouse enter)*

```
@HostListener('mouseenter') onMouseEnter() {
  this.highlight(this.highlightColor || 'red');
}
```

ここにディレクティブクラスの最新バージョンがあります。

*src/app/highlight.directive.ts (抜粋)*

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

@Directive({
  selector: '[myHighlight]'
})
export class HighlightDirective {

  constructor(private el: ElementRef) { }

  @Input('myHighlight') highlightColor: string;

  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || 'red');
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}
```

## それを試すハーネスを書く

この指令がどのように実際に動作するかは想像するのが難しいかもしれません。このセクションでは、`AppComponent` をハーネスに変換し、ラジオボタンでハイライトカラーを選択し、色の選択肢をディレクティブにバインドします。

app.component.html を次のように更新します。

*src/app/app.component.html (v2)*

```
<h1>My First Attribute Directive</h1>

<h4>Pick a highlight color</h4>
<div>
  <input type="radio" name="colors" (click)="color='lightgreen'">Green
  <input type="radio" name="colors" (click)="color='yellow'">Yellow
  <input type="radio" name="colors" (click)="color='cyan'">Cyan
</div>
<p [myHighlight]="color">Highlight me!</p>
```

AppComponent.colorを初期値がないように修正します。

*vsrc/app/app.component.ts (class)*

```
export class AppComponent {
  color: string;
}
```

実際のハーネスと指令があります。

![Highlight v.2](https://angular.io/generated/images/guide/attribute-directives/highlight-directive-v2-anim.gif)

## 2番目のプロパティにバインドする

このハイライトディレクティブには、カスタマイズ可能なプロパティが1つあります。実際のアプリでは、もっと必要な場合があります。

現時点では、ユーザがハイライトカラーを選択するまでのデフォルトカラー、つまり「赤」としてハードコードされています。テンプレートの実装担当者にデフォルトの色を設定しているカたちですね｡
`HighlightDirective` に `defaultColor` という2番目の入力プロパティを追加します。

*src/app/highlight.directive.ts (defaultColor)*
```
@Input() defaultColor: string;
```

ディレクティブの `onMouseEnter` を変更して、`highlightColor` で強調表示し、次に`defaultColor` で強調表示し、両方のプロパティが未定義の場合は` "red"` に戻します。

*src/app/highlight.directive.ts (mouse-enter)*

```
@HostListener('mouseenter') onMouseEnter() {
  this.highlight(this.highlightColor || this.defaultColor || 'red');
}
```

`myHighlight` 属性名にバインドしているときに、どのようにして2番目のプロパティにバインドしますか？

コンポーネントの場合と同様に、必要に応じて、テンプレート内で文字列を組み合わせることによって、多くのディレクティブプロパティバインディングを追加できます。開発者は`AppComponent.color` にバインドし、デフォルトの色として `"violet"` にフォールバックするために、次のテンプレートHTMLを書き込むことができます。

*src/app/app.component.html (defaultColor)*

```
<p [myHighlight]="color" defaultColor="violet">
  Highlight me too!
</p>
```

Angularは、`DefaultColor` バインディングが `@Input` デコレータで公開されているため、`HighlightDirective` に属していることを認識しています。

一通りのコーディングが完了したら、実際に機能するハーネスは次のとおりです。

![Final Highlight](https://angular.io/generated/images/guide/attribute-directives/highlight-directive-final-anim.gif)

## まとめ

このページでは次の要点を解説しました｡

- 要素の動作を変更する属性ディレクティブを作成します。
- ディレクティブをテンプレートの要素に適用します。
- ディレクティブの動作を変更するイベントに応答します。
- ディレクティブに値をバインドします。

最終的なソースコードは次のとおりです。

ここから、実際のソースコードを見たり、ダウンロードしたりすることもできます｡

### Appendix: @Inputを追加する理由

このデモでは、`hightlightColor` プロパティは `HighlightDirective` の入力プロパティです。エイリアスなしで適用されたことがわかりました。

*src/app/highlight.directive.ts (color)*

```
@Input() highlightColor: string;
```

エイリアスとしてセットするのも紹介しました:

*src/app/highlight.directive.ts (color)*

```
@Input('myHighlight') highlightColor: string;
```

どちらの方法でも、`@Input` デコレータはAngularに、このプロパティはパブリックであり、親コンポーネントによるバインドに使用可能であることを伝えます。 `@Input` を指定しない場合、Angularはプロパティにバインドすることを拒否します。

`@Input` を使用する前に、テンプレートHTMLをコンポーネントプロパティにバインドしました。違いはなんでしょうか？

違いは信頼の問題です。 Angularは、コンポーネントのテンプレートをコンポーネントに属するものとして扱います。コンポーネントとそのテンプレートは、暗黙のうちに互いに信頼し合います。したがって、コンポーネント自身のテンプレートは、`@Input` デコレータの有無にかかわらず、そのコンポーネントの任意のプロパティにバインドできます。

しかし、コンポーネントや指令は、他のコンポーネントや指令を盲目的に信頼すべきではありません。デフォルトでは、コンポーネントまたはディレクティブのプロパティはバインディングから隠されています。Angularバインディングの観点からは非公開です。 `@Input` デコレータで装飾されると、プロパティはAngularバインディングの観点からパブリックになります。その後、他のコンポーネントやディレクティブにバインドすることができます。

`@Input` がバインディング内のプロパティ名の位置によって必要であるかどうかを知ることができます。

- 等号 (=) の右側のテンプレート式に現れると、それはテンプレートのコンポーネントに属し、`@Input` デコレータを必要としません。
- 等号 (=) の左を角括弧（[]）で囲っている場合、このプロパティは他のコンポーネントまたはディレクティブに属します。そのプロパティは `@Input` デコレータで飾られなければなりません。

次に、その推論を次の例に適用します。

*src/app/app.component.html (color)*

```
<p [myHighlight]="color">Highlight me!</p>
```

- 右側の式の `color` プロパティはテンプレートのコンポーネントに属します。テンプレートとそのコンポーネントはお互いを信頼します。`color` プロパティは `@Input` デコレータを必要としません。
- 左側の `myHighlight` プロパティは、テンプレートのコンポーネントのプロパティではなく、`HighlightDirective` のエイリアス化されたプロパティを参照します。信頼の問題があります。したがって、ディレクティブプロパティは `@Input` デコレータを保持する必要があります。