AngularのReactiveFormとAngularMaterialを組み合わせる

# はじめに
Vue人気も一段落ついた感があるので、ここからAngularを盛り上げる（？）ためにも何か投稿せねば！！（その2）

Angularの強みといえるフォーム周りですが、Validationを入れるならやっぱり`ReactiveForm`の`FormGroup`＋`FormBuilder`が分かりやすいし、楽だと思います。

それをMaterialDesign用コンポーネントを提供してくれる[Angular Material](https://material.angular.io/)のフォーム系に使いたい。だけど[公式ドキュメントのExample](https://material.angular.io/components/form-field/examples)には単純な`FormControl`を利用したものしかないです。

というわけでやってみました。

# 準備
まずは`ng new`するなりして、Angularが書ける環境を用意して、主役の「Angular Material」を追加します。

```console
$: npm install --save @angular/material @angular/cdk @angular/animations
```

※Yarn派は`yarn`コマンドに置き換えてください。

`app.module.ts`に`BrowserAnimationsModule`と使用する予定の各Materialコンポーネント（`Mat○○Module`）を追加します。

```typescript:app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import {
  MatButtonModule,
  MatFormFieldModule,
  MatInputModule,
  MatSelectModule,
} from '@angular/material';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    CommonModule,
    BrowserModule,
    BrowserAnimationsModule,
    ReactiveFormsModule,
    MatButtonModule,
    MatFormFieldModule,
    MatInputModule,
    MatSelectModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

加えてcssも適用します。

```css:styles.css
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
```

テーマとか各コンポーネントの詳しいことは[公式ドキュメント](https://material.angular.io/)を確認ください。

# コンポーネント
~~面倒くさいので~~今回は`AppComponent`に全部書いちゃいます。

フォームの項目およびValidationタイプは

- お名前（テキスト）　 必須
- 性別（セレクト）　　 必須＋「未選択（0）」以上
- Eメール（テキスト）　メールタイプ

と何の芸もない組み合わせにしています。

`ReactiveForm`なので各入力項目のフォーカスを抜けるとValidationが走り、エラーがあれば表示されます。

```typescript:app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms';

// 性別用型
interface Gender {
  value: number;
  display: string;
}

@Component({
  selector: 'app-root',
  template:
  `
  <form [formGroup]="editForm" (ngSubmit)="onSave()" class="form">
    <mat-form-field class="form-field">
      <input matInput placeholder="お名前" formControlName="name" required>
      <mat-error *ngIf="name.invalid && (name.dirty || name.touched)">お名前は必須です</mat-error>
    </mat-form-field>
    <mat-form-field class="form-field">
      <mat-select placeholder="性別" formControlName="genderValue" required>
        <mat-option *ngFor="let gender of genders" [value]="gender.value">
          {{ gender.display }}
        </mat-option>
      </mat-select>
      <mat-error *ngIf="genderValue.invalid && (genderValue.dirty || genderValue.touched)">性別は必須です</mat-error>
    </mat-form-field>
    <mat-form-field class="form-field">
      <input matInput placeholder="Eメール" formControlName="mail">
      <mat-error *ngIf="mail.invalid && (mail.dirty || mail.touched)">Eメールが不正です</mat-error>
    </mat-form-field>
    <div class="action">
      <button type="submit" mat-button color="primary" [disabled]="editForm.invalid">SAVE</button>
    </div>
  </form>
  `,
  styles: [
    `
      :host {
        display: box;
        box-sizing: border-box;
      }
      .form {
        margin: 8px;
        padding: 8px;
      }
      .form-field {
        width: 100%;
      }
      .action {
        display: flex;
        justify-content: space-around;
      }
    `,
  ]
})
export class AppComponent implements OnInit {
  // 性別一覧
  genders: Gender[] = [
    {
      value: 0,
      display: '未選択',
    },
    {
      value: 1,
      display: '漢',
    },
    {
      value: 2,
      display: '女性',
    },
    {
      value: 3,
      display: 'どちらでもない',
    },
  ];

  // FormGroup
  editForm: FormGroup;


  // 各フォームコントロールを取得するGetter
  get name(): AbstractControl {
    return this.editForm.get('name');
  }
  get genderValue(): AbstractControl {
    return this.editForm.get('genderValue');
  }
  get mail(): AbstractControl {
    return this.editForm.get('mail');
  }

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.editForm = this.fb.group({
      name: ['', [Validators.required]],
      genderValue: [0, [Validators.required, Validators.min(1)]],
      mail: ['', [Validators.email]],
    });
  }

  onSave() {
    console.log(this.editForm.value);
  }
}
```

# RUN
```console
$: ng serve
```

初期表示
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94252/4b97d444-aeac-050d-9b84-dee2f9f5d04c.png)

All Invalid
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94252/ec1fe13b-d69c-07cd-a429-1f8ff8395659.png)

All Valid
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94252/91bffee2-d22c-d1c5-f1ae-464d1885d4e6.png)

この状態で「SAVE」をクリックするとブラウザの開発用コンソールに`{name: "てすとくん", genderValue: 1, mail: "testkun@test.com"}`と表示されるはずです。


# おわりに
いつも思うけど日本語表記にするとMaterial感が目減りする気がするのは私だけでしょうか？

Angularはフォーム周りは本当に強いと思ってるのでもっとAngularが流行ればいいなぁ。