属性ディレクティブ・構造ディレクティブを自作してみる

# はじめに
ディレクティブが全く理解できていなかったので勉強しました。
特に構造ディレクティブの考え方が分かりにくいので、`*`と`[]`の意味の違いに気を付ける必要があると感じました。
Angular公式などをみて書いていますが、あくまで勉強したときに書いたメモ書きなのであしからず。

# 属性ディレクティブを自作する
属性ディレクティブは適用した要素の見た目（CSS）を変えたりイベントハンドラを設定したりできる。ngStyle等が例。
構造ディレクティブは要素を追加したりできる。ngIfとかが例。

コマンド（例：`ng generate directive myColored`）

```
ng generate directive ディレクティブ名
```

ここではelementRefを使ってDOMを操作してみる

```src\app\my-colored.directive.ts
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
import { style } from '@angular/animations';

@Directive({
  selector: '[appMyColored]'
})
export class MyColoredDirective implements OnInit {
  constructor(private elementRef: ElementRef) { }

  ngOnInit() {
    this.elementRef.nativeElement.style.backgroundColor = '#cf0';
  }
}
```

モジュールに登録することでディレクティブが使えるようになる（declarationsに追加する）

```src\app\app.module.ts
...
import { MyColoredDirective } from './my-colored.directive';

@NgModule({
  declarations: [
    AppComponent,
    ...
    GrepPipe,
    MyColoredDirective,
  ],
```
ディレクティブを付けると適応できる

```app.component.html
<form>
  <label appMyColored>キーワード：
...
```
## パラメーター付きのディレクティブ
単純に@Inputを使えばいい

```src\app\my-colored.directive.ts
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
import { style } from '@angular/animations';

@Directive({
  selector: '[appMyColored]'
})
export class MyColoredDirective implements OnInit {
  @Input() myBgColor = '#cf0';  //デフォルト値を設定している

  constructor(private elementRef: ElementRef) { }

  ngOnInit() {
    this.elementRef.nativeElement.style.backgroundColor = this.myBgColor;
  }
}
``` 

属性名をそのまま指定しても良い。属性バインディングを使うこともできる

```src\app\app.component.html
<form>
  <label appMyColored myBgColor="#ee0">キーワード：
    <input appMyColored [myBgColor]="'#cc0'" type="text" #txt size="15">
```

## ディレクティブ名で属性を設定できるようにする
ディレクティブ名で属性を指定できるようにする場合も多い。  
ただし、ただ単に@Inputでディレクティブ名と属性名を同じにするだけなのでディレクティブ名だけでは1つのパラメーターしか受け取ることができない。
（@Inputはいくつでも使えるためディレクティブ名と属性名を合わせることにこだわらなければいくつでも属性を指定できる）

```src\app\my-colored.directive.ts
@Directive({
  selector: '[appMyColored]'
})
export class MyColoredDirective implements OnInit {
  @Input(`appMyColored`) myBgColor = '#cf0';
...
```

```src\app\app.component.html
<form>
  <label appMyColored='#0cc'>キーワード：
    <input [appMyColored]="'#00e'" type="text" #txt size="15">
```

ディレクティブ名を属性名と同じにしつつ、他の属性も使う場合は以下のようにする

```src\app\my-colored.directive.ts
@Directive({
  selector: '[appMyColored]'
})
export class MyColoredDirective implements OnInit {
  @Input(`appMyColored`) myBgColor = '#cf0';
  @Input() hoge = 'hoge';

  constructor(private elementRef: ElementRef) { }

  ngOnInit() {
    this.elementRef.nativeElement.style.backgroundColor = this.myBgColor;
    console.log(this.hoge);
  }
```

```src\app\app.component.html
<form>
  <label appMyColored='#0cc'>キーワード：
    <input [appMyColored]="'#00e'" [hoge]="'hoo'" type="text" #txt size="15">
```

## イベント処理をディレクティブに追加する
属性ディレクティブにはイベントリスナーを設定できる。  
イベントリスナーは`@HostListener`を使って定義する。
要素にクリックなどのイベントが発生したときの動作（イベントハンドラ）をディレクティブで設定することができる。

```src\app\my-colored.directive.ts
@Directive({
  selector: '[appMyColored]'
})
export class MyColoredDirective /*implements OnInit*/ {
  @Input(`appMyColored`) myBgColor = '#cf0';
  @Input() hoge = 'hoge';

  constructor(private elementRef: ElementRef) { }
  //マウスが要素内に入った時
  @HostListener('mouseenter') enableBgColor() {
    this.elementRef.nativeElement.style.backgroundColor = this.myBgColor;
  }
  //マウスが要素内から外れたとき
  @HostListener('mouseleave') disableBgColor() {
    this.elementRef.nativeElement.style.backgroundColor = '';
  }
}
```
この例ではマウスポインタが要素内に入った時背景が変わり、マウスポインタが要素内から出たとき背景が白（''）に戻る

```src\app\app.component.html
<form>
  <label appMyColored='#0cc'>キーワード：
    <input [appMyColored]="'#00e'" [hoge]="'hoo'" type="text" #txt size="15">
```

`@HostListener`には引数を指定することができる。  
引数は配列にまとめて@HostListenerに登録して、イベントハンドラの仮引数で利用する。
引数は`string[]`で指定する必要があるため注意（以下の例でいうと`'$event.target'`の部分）

```文法
@HostListener(name[, args])
```
```src\app\my-colored.directive.ts
@HostListener('mouseleave', ['$event.target', 'hoge']) disableBgColor(element, hoge) {
  this.elementRef.nativeElement.style.backgroundColor = '';
  console.log(element); //<input _ngcontent-vvh-c55="" type="text" size="15" ng-reflect-my-bg-color="#00e" ng-reflect-hoge="hoo" style="">
  console.log(hoge);
}
```

# 構造ディレクティブを作成する
例えば、先ほど作成した属性ディレクティブを「*」構文で呼び出してみる。

```src\app\my-colored.directive.ts
@Directive({
  selector: '[appMyColored]'
})
export class MyColoredDirective /*implements OnInit*/ {
  @Input(`appMyColored`) myBgColor = '#cf0';
  @Input() hoge = 'hoge';

  constructor(private elementRef: ElementRef) { }
  //マウスが要素内に入った時
  @HostListener('mouseenter') enableBgColor() {
    this.elementRef.nativeElement.style.backgroundColor = this.myBgColor;
  }
  //マウスが要素内から外れたとき
  @HostListener('mouseleave') disableBgColor() {
    this.elementRef.nativeElement.style.backgroundColor = '';
  }
}
```

```app.component.html
<span *appMyColored>これは画面に表示されない</span>
```
このように書くとエラーにはならないが、画面にはappMyColoredを使ったspanタグの部分は画面に表示されなくなる。これはAngularの`*`構文は、内部的には**ターゲット要素を<ng-template>要素に展開するためのシンタックスシュガー**であるため。  
この例ではテンプレート化された要素（ng-template内に書かれた要素）を画面に反映させる処理が記載されていないため、画面には表示されない。
補足すると、先ほどのapp.component.htmlの例は以下のように書き換えられるイメージらしい。

```app.component.html
<ng-template appMyColored>
  <span>これは画面に表示されない</span>
</ng-template>
```


## 構造ディレクティブを実装する
構造ディレクティブを作るためには、ディレクティブ内でTemplateRefオブジェクトとViewContainerRefオブジェクトを使う。  
ViewContainerRefはテンプレート化された要素を挿入する領域を表す。（イメージ的にはng-templateがある場所を表す）
TemplateRefはテンプレート化された要素（つまり、構造ディレクティブを付けた要素。言い方を変えるとng-templateの下にある要素）を表す。
基本的にはViewContainerRefで要素を表示したいところを参照して、ViewContainerRefにTemplateRef（画面に表示する要素）を追加する流れになる。
要素を領域に差し込むためには以下の構文を使う。
createEmbeddedViewメソッドは、引数のテンプレートを基に要素を作成してviewContainerRefの領域に埋め込む。

構文：`viewContainerRef.createEmbeddedView(templateRef)`

例

```src\app\judge-number.directive.ts
import { Directive, Input, OnChanges, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appJudgeNumber]'
})
export class JudgeNumberDirective {
  //数字を設定する
  @Input('appJudgeNumber') number: number;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainerRef: ViewContainerRef
    ) { }

  //@Input経由で入力値が（再）設定されたとき
  //appJudgeNumber属性を変更したときにテンプレートの表示・非表示を判定
  ngOnChanges() {
    //数字が5より小さいときclearする
    if(this.number < 5) {
      this.viewContainerRef.clear();
    } else {
      this.viewContainerRef.createEmbeddedView(this.templateRef);
    }
  }
}
```

```src\app\app.component.html
<div *appJudgeNumber="number">
  <h2>これが構造ディレクティブです</h2>
  <p>*はng-templateに要素を展開するシンタックスシュガーです</p>
</div>
```

numberが5以上の時のみ画面に要素が表示される

```
これが構造ディレクティブです
*はng-templateに要素を展開するシンタックスシュガーです
``