【Angular】親から子コンポーネントに値を渡すリアクティブフォーム作ってみた

#はじめに
`Angular`で作ったフォームでバックエンドとやりとりをさせたいなと思い、
リアクティブフォームの実装をしてみました！
ハマったところとかポイントも書きながら
備忘録的な形で残します。


[Angularのローカル上のセットアップ](https://qiita.com/ichi_zamurai/items/b4c7ecc0189ed4379464#angular%E3%81%AE%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E4%B8%8A%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97)については、こちらの記事もご覧ください〜

https://qiita.com/ichi_zamurai/items/b4c7ecc0189ed4379464#angular%E3%81%AE%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E4%B8%8A%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97

#TL;DR
- ファイル・ディレクトリ構成
- コードと実装について
- 実践とその確認
- まとめ


#ディレクトリ構成
```
login
├── form-challenge.module.ts
└── page
    ├── login.component.ts
    ├── login.component.html
    ├── login.component.less
    └── message
        ├── ng-message.component.ts
        ├── ng-message.component.html
        └── ng-message.component.less
```
※関連のある最小限のファイルのみ記載。(routing.moduleとか省いてます。)

#コードと実装
形としては
親：`login.component`から子：`ng-message.component`に`@Input()`を使って、
親の`FormGroup`のプロパティを渡し、子コンポーネント側で各input要素のバリデーションチェックをする。

##Typescript: `login.module`
使用する`module`の宣言、importするファイル

```typescript:login.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from "@angular/common";
import { RouterModule } from "@angular/router";
import { LoginRoutingModule } from './login-routing.module';
import { LoginComponent } from './login/page/login.component';
import { NgMessageComponent } from './login/page/message/ng-message.component';

@NgModule({
  declarations: [
    // Product Components
    LoginComponent, // 親コンポーネント
    NgMessageComponent // 子コンポーネント
  ],
  imports: [
    // Angular Common Modules
    CommonModule,
    RouterModule,
    ReactiveFormsModule, //FormControlとか使える
    FormsModule, // FormのValidatorとか色々使えるように
    // Product Routing Modules
    LoginRoutingModule
  ],
  entryComponents: [
    // Components Generated Dynamically
  ],
  providers: [
    // Product Services (DI)
  ],
  bootstrap: [],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class LoginModule { }

```

##Typescript: `login.component`
`login.component.html`と連動する`Typescript`ファイル

```typescript:login.component.ts
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
import { Subject, Subscription } from "rxjs";
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { NgMessageComponent } from './message/ng-message.component';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.less'],
  encapsulation: ViewEncapsulation.None
})
export class LoginComponent implements OnInit, OnDestroy {
  // Constructor DI
  constructor() { }

  // Property Definition
  destroyed$ = new Subject();
  private subscriptions: Array<Subscription> = [];
  pageReady: boolean = false;
  formGroup: FormGroup;

  //To prevent memory leak
  ngOnDestroy(): void {
    this.destroyed$.next();
    this.destroyed$.complete();
    this.subscriptions.forEach(removeSubscription => removeSubscription.unsubscribe());
  }

  //Initialization
  ngOnInit(): void {
    this.formGroup = new FormGroup({
      email: new FormControl('', [
        Validators.email, // e-mailフォーマットチェック
        Validators.required // 必須チェック
        // this.originalValidatorHere() // 任意のバリデーションも記載できる
      ]),
      password: new FormControl('', [
        Validators.required,　// 必須チェック
        Validators.minLength(6) // 最低6文字
      ])
    });
    this.pageReady = true;
  }

  public get email(): FormControl {
    return this.formGroup.get('email') as FormControl;
  }

  public get password(): FormControl {
    return this.formGroup.get('password') as FormControl;
  }

  onSubmit() {
      //Do some process on Submit event
    });
  }
}

```
##HTML: `login.component`
フォームの実装html。
バリデーションエラーメッセージ表示用の子コンポーネント`app-ng-message`のタグが仕込まれている。
`formGroup`とバインドし、`submit`が発火されたら`onSubmit()`をコール。

```html:login.component.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="content-type" charset="utf-8">
  <title>Test Application</title>
</head>
<body>
<ng-container *ngIf="pageReady">
<h1>リアクティブフォームを試すアプリ</h1>
  <form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
    <div>
      <div>
        Eメール：<input type="text" formControlName="email" required>
      </div>
      <div>
        パスワード：<input type="password" formControlName="password" required>
      </div>
      <div>
        <app-ng-message [formGroup]="this.formGroup"></app-ng-message>
      </div>
      <div>
        <button type="submit" [disabled]="formGroup.invalid">Login</button>
      </div>
    </div>
  </form>
</ng-container>
</body>
</html>
```

##Typescript: `ng-message.component`
`ng-message.component.html`と連動する`Typescript`ファイル

```typescript:ng-message.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { FormGroup, FormControl, AbstractControl } from '@angular/forms';

@Component({
  selector: 'app-ng-message',
  templateUrl: './ng-message.component.html',
  styleUrls: ['./ng-message.component.less']
})
export class NgMessageComponent implements OnInit {
  // 親からFormGroup受取用プロパティ
  @Input() formGroup: FormGroup;
  email: FormControl;
  password: FormControl;

  constructor() { }

  // Initialization
  ngOnInit() {
    this.email = this.formGroup.controls.email as FormControl;
    this.password = this.formGroup.controls.password as FormControl;
  }
}
```
##HTML: `ng-message.component`
フォーム内容が`invalid`、かつ変更が加えられているorフォーカスした時にメッセージ表示する。
※`.dirty`：フォーム／要素が変更されている場合にtrueを返す
※`.touched`：フォーム／入力要素に一度でもフォーカスしたか、一度もフォーカスしていないかを判定する

```html:ng-message.component.html
<div *ngIf="email.invalid && (email.dirty || email.touched)">
  <span *ngIf="email.hasError('required')">
  emailは必須です。<br>
  </span>
  <span *ngIf="email.hasError('email')">
  e-mailアドレスのフォーマットにしてください。(xxx@hogehoge.com)<br>
  </span>
</div>
<div *ngIf="password.invalid && (password.dirty || password.touched)">
  <span *ngIf="password.hasError('required')">
    パスワードは必須です。<br>
  </span>
  <span *ngIf="password.hasError('minlength')">
    最低6文字です。<br>
  </span>
</div>
```


#早速確認してみる
###AngularのWebサーバーを起動
```shell
$ cd /Users/username/src/github.com/web/app
$ ng serve --open
```
###無事表示された
デフォルトの感じ。
![スクリーンショット 2021-04-02 10.40.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/da565ae8-7403-0202-dcb9-4b7f446a5fe7.png)
※URLが`jwt-challenge`となっていますが、今作っているアプリのroutingなので、お気になさらず

###いろんなパターンで入力してみる！
####１.`email`にフォーカス=>アンフォーカス。
![スクリーンショット 2021-04-02 10.50.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/882b73d1-2852-4fbd-302f-1c616672bac6.png)
####２.`password`もフォーカス=>アンフォーカス。
![スクリーンショット 2021-04-02 10.50.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/ce2d71a7-ea6c-c4dd-cf56-170c047d596a.png)
####３.`email`に値入れてみる。
![スクリーンショット 2021-04-02 10.50.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/96c8e6b3-adde-d923-9968-932f50397463.png)
####４.`password`にも値入れてみる。
![スクリーンショット 2021-04-02 10.51.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/539a7e87-4a5d-6789-bb7a-12c7c716f34d.png)


###良い感じに動いている✨


#ハマったポイント
- 子コンポーネントに`FormControl`を単体で渡すのか、`FormGroup`自体を渡すのかで、悩んだ。
    - 今回の実装的には、`FormGroup`自体を渡す方がやりやすかった。
- `ngSubmit`のイベントで送れる`$event`は単純に`javascript`で取れる`event`のオブジェクト。
- 子コンポーネントのタグを複数親のhtmlに記述したら複数のメッセージが表示されてしまった（当たり前だけどw）
- `module.ts`に親と子のコンポーネントを入れるのと、`FormsModule`と`ReactiveFormsModule`を入れることを忘れない。


#まとめ
やっとログインフォームの原型が作れました。
これでバックエンドとやりとりできる準備ができたので、
今度は、`jwt`の取り扱いに挑戦してみたいと思います〜✨

jwtの概念的には理解はしてるけど、実践的には全然まだまだできていないので。
下記参考までに〜

https://qiita.com/ichi_zamurai/items/8f0887c7bfb4c206b795


以上、
ありがとうございました！