Angular: @ng-select/ng-selectを利用したセレクトボックスのバリデーションチェック

# はじめに
Angular6 + bootstrap4 でフロント開発中。
普段は、できるだけテンプレート駆動フォームで開発しています。

この度、セレクトボックスで複数選択可能のかっちょいい見た目にしたく、```@ng-select/ng-select```を導入し実装した際に、バリデーションチェックのコメント表示でハマったのでそのへん含めてまとめています。
```@ng-select/ng-select```の詳しい[使い方はこちら](https://www.npmjs.com/package/@ng-select/ng-select#validation-state)を参照。

※全く別のパッケージで```ng-select```という名前のものもあるので気をつけてください！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/157350/d73c7965-ffbc-68df-7578-5da9deabb5ac.png)


# テキストエリア
普段はこんな感じで、```required```といったAngularの標準バリデーションチェック機能を利用しています。
エラー時のメッセージはbootstrapの```invalid-feedback```のデザインを適用して表示しています。

```textarea.component.html
<div class="form-group row">
  <label class="col-sm-3 col-form-label" for="userEmail">ユーザメールアドレス</label>
  <div class="col-sm-9">
    <input type="text" class="form-control" name="userEmail" [(ngModel)]="user.user_email" #userEmail="ngModel"
      [ngClass]="{ 'is-invalid': f.submitted && userEmail.invalid }" required email/>
    <div *ngIf="f.submitted && userEmail.invalid" class="invalid-feedback">
      <div *ngIf="userEmail.errors.required">この項目は必須です。</div>
      <div *ngIf="userEmail.errors.email">メールアドレスの形式で入力してください。</div>
    </div>
  </div>
</div>
```

# ng-selectを適用したセレクトボックス
前述の通り、```@ng-select/ng-select```を使ってセレクトボックスの機能＆見た目を拡張しているのですが、テキストエリアのバリデーションと同じようにできるだろうと思っていたら意外とハマりました。。

まず、完成形です。

```selectbox.component.html
<div class="form-group row">
  <label class="col-sm-3 col-form-label" for="account">アカウント選択</label>
  <div class="col-sm-9">
    <ng-select [items]="accounts | async" name="account" bindLabel="name" autofocus bindValue="id"
      [(ngModel)]="user.account_id" required #account="ngModel" (change)="changeAccount($event)"></ng-select>
    <div *ngIf="f.submitted && account.invalid" class="invalid-feedback d-block">
      この項目は必須です。
    </div>
  </div>
</div>
```

```selectbox.component.ts
accounts: Promise<any>;    // ほんとはanyダメよ
public ngOnInit() {
    this.accounts = this.accountService.getData();
}
```

### 注意点１
・導入時のハマりポイント！
これはあくまで私の環境だけの可能性もありますので、適宜読み替えてください。

- angular.jsonにcssのパスを記載しても適用されない

→公式にできるって書いてるのにできませんでした。。
結局、公式のアナウンス通り、styles.scssにimport文を書くと無事適用されました。

- module.ts を分割していると適用範囲がわかりにくい

こちらは、```ng-select```を適用したいcomponentの```直近のmodule.ts```にng-selectをimportしないとエラー出ちゃいました。

### 注意点2
・バリデーションチェック時のcssを追記する必要がある！
公式にアナウンスがあったのですが、私が読み飛ばしていました。。

```styles.scss
ng-select.ng-invalid.ng-touched .ng-select-container {
    border-color: #dc3545;
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px #fde6e8;
}
```

基本的にng-selectでも```required```などのバリデーションチェックパラメータは有効です。
しかし、バリデーションチェックにかかったときに付与されるクラスが、```ng-invalid```というクラスに変わるのでそこが注意ポイントです。
もちろん、bootstrapの標準ではないので、自分で追記する必要があります。

### 注意点３
・```invalid-feedback```が効かない！
注意２のバリデーションチェック時のクラス名が変わることが分かっていればすぐ解決できたのですが、ハマってしまいました。。
[bootstrap4](https://getbootstrap.com/docs/4.0/components/forms/)によると```invalid-feedback```は、```.is-invalid```や```.is-valid```に反応してスタイルを 適用する/しない を制御してるみたいです。

ng-selectは、対象となるセレクトボックスのオブジェクトに対して、Angularのinvalidパラメータは付与してくれますが、bootstrapの.is-invalidは付与してくれないようです。

しかし、セレクトボックスのエラーメッセージだけcss書くなんてやりたくないので、調べていると、```.d-block```というのを見つけました。
これは、強制的に```invalid-feedback```を有効化してくれるクラスらしく、こいつを使うことで、
div要素の出し入れは通常通りAngularのngIfディレクティブにまかせて、エラーメッセージの適用はbootstrapという形ができました。

```該当箇所.html
<div *ngIf="f.submitted && account.invalid" class="invalid-feedback d-block">
  この項目は必須です。
</div>
```

# さいごに
セレクトボックスにこだわったあまり、```@ng-select/ng-select```と戦ったよって話でした。
サードパーティ製のフォーム系パッケージを使うときは、いろんなものとバッティングすることを考えて技術選択しないとだな、と反省しました。