Angular の 2種類の form についてまとめた (リアクティブフォーム / テンプレート駆動フォーム)


# はじめに

Angular には2つのフォームスタイルがありそれぞれ使用方法や使い勝手もかなり違っています。
公式でもそれぞれについて言及がありますが、 テンプレート駆動フォームについては日本語の公式ページがないことから
この記事でリアクティブフォームとテンプレート駆動フォームの双方についてまとめました。

この記事ではこれら2つのフォームの使用方法について理解することを目的としています。

完成したソースは https://github.com/Yu0614/angular-form-differences にてMITライセンスで公開されています。

### リアクティブフォーム

#### 使用方法

'@angular/forms' の提供する FormBuilder, FormControl, FormGroup を使用します。

-   ① コンポーネントと HTML 要素の form を formGroup で指定し、リンクさせる。
-   ② formControlName を指定し、コンポーネントのフォーム属性とリンクさせる。

```html
<!-- ① using formGroup to link form with component -->
<form class="form" [formGroup]="loginForm" (ngSubmit)="submit()">
    <!-- ② using formControlName to link form with component's attribute -->
    <input type="email" placeholder="mail" formControlName="mail" required />
    <input
        type="password"
        placeholder="Password"
        formControlName="password"
        required
    />
</form>
```

-   ③-1 フォームで使用する対象の属性を FormBuilder 経由で初期化する

```typescript
// Using FormBuilder to link and initialize form values
    public loginForm = this.builder.group({
        mail: [''],
        password: [''],
    });
```

-   ③-2 フォームで使用する対象の属性を FormGroup, FormControl 経由で初期化する

```typescript
// This also possible though it could be troublesome (using instance..)
    public loginForm = new FormGroup({
        mail: new FormControl(''),
        password: new FormControl(''),
    });
```

#### form のデータ更新

-   FormGroup のメソッド経由で値を更新する

```typescript
// 特定の要素のみ値を更新する際には patchValue を使用する
this.loginForm.patchValue({
    mail: 'sample@example.com',
});

// すべての要素を更新する際には setValue を使用する
this.loginForm.setValue({
    mail: 'sample@example.com',
    password: 'password',
});
```

#### form の validate

-   formGroup で Validators 経由の validate をかける
-   input 要素に required を追加する

```typescript
// Validators 経由で Validate をかける
public loginForm = this.builder.group({
        mail: ['', Validators.email], // validates whether email format or not
        password: ['', Validators.required], // validates form filled or not
    });
```

```html
<!-- html の input要素に require を追加 -->
<input type="email" placeholder="mail" formControlName="mail" required />
<input
    type="password"
    placeholder="Password"
    formControlName="password"
    required
/>
```

### テンプレート駆動フォーム

#### 使用方法

html側、コンポーネント側双方での準備が必須。 リアクティブフォームと比べて ngModel を定義するなど少し煩雑。

-   ① コンポーネントではデータバインドに使用する フォームのモデルを作成する。
-   ② html側で form要素に ngForm を指定し、コンポーネントとリンクさせる。
    -    ngModel で コンポーネントで用意したモデルと関連付ける。
-   ③ viewChild を使用してformとしてバインドされたフォームの入力値を参照する。
  
```typescript
  // html 要素の参照
  @ViewChild('form') form: NgForm;
``` 

```html
<!-- ① using ngForm to link form with component -->
        <form class="form" #form="ngForm" (ngSubmit)="submit()">
            <!-- ② using ngModel to link value with component model -->
            <input
                type="email"
                placeholder="mail"
                [(ngModel)]="model.mail"
                name="mail"
                #mail="ngModel"
                required
                email
            />
</form>
```

#### form のデータ更新

-   ngModel で指定したモデル経由で値を更新することができる

```typescript
// model の値を更新
this.model.mail = 'example@example.com';
this.model.password = 'invalid-password';
```

#### form の validate

-   バインドした form 経由で validate をかけたいフォーム要素の errors を確認する
    -   以下で説明する required,email を指定していた場合はその要素の errors を確認することで validateの結果を取得できる
     
-   input 要素に required, email を追加する
    -   required を追加することで必須要素であることをformに伝えることができる
    -   email を追加することで email のパターンマッチングを行ってくれる

```typescript
// 指定していた form要素を参照し、付与したhtmlのvalidate結果を取得する(required,email)
get emailInvalid() {
    return this.form?.form?.controls?.mail?.errors?.required ? true : false;
}
get emailPatternInvalid() {
    return this.form?.form?.controls?.mail?.errors?.email ? true : false;
}

// password の validate 結果の取得
get passwordInvalid() {
    return this.form?.form?.controls?.password?.errors?.required ? true : false;
}

```

```html
<!-- html の input要素に required,email を追加 -->
<input
    type="email"
    placeholder="mail"
    [(ngModel)]="model.mail"
    name="mail"
    #mail="ngModel"
    required
    email
/>
<input
    type="password"
    placeholder="password"
    [(ngModel)]="model.password"
    name="password"
    #password="ngModel"
    required
/>
```

### まとめ

* より直感的にわかりやすいのは リアクティブフォーム
    * 使うまでの準備や手間がテンプレート駆動フォームより少ない
* validate を行うにあたっては双方の手間はそう変わらない

* ディレクティブや細分化したコンポーネントに値を渡してフォームを作る場合などは テンプレート駆動の方が使いやすいかもしれない


=> 簡単・シンプルなフォームならリアクティブフォーム、より複雑なことをする場合はテンプレート駆動で作っておくとリファクタ含めやりやすいかも知れない