Ionicで簡易コーポレートサイト（アプリ）を作成する #2 -Contact画面編-

# 概要
トップページと同様にお問合せページをIonicで再現します。
Contactコンポーネントを作成し、バリデーションを実装します。

# 完成イメージ
> この時点ではタブがないため、`ionic serve`でchrome ブラウザから確認できる状態です。

<img width=300 alt="000060.jpg" src="https://dl.dropbox.com/s/9c28nmkj0t2ywbd/000060.jpg" />

<img width="300" alt="000050.jpg" src="https://dl.dropbox.com/s/gvydu5jyefon2ek/000050.jpg　">


# Contact画面作成
同様にContactページを作成していきます。

## Contactページ
contactページ作成に必要なファイルをコマンドで作成します。
作業フォルダにいることを確認して実行します。

```
ionic generate page contact
```
src/にcontactフォルダとファイルが追加されました。

<img width="300" alt="000020.jpg" src="https://dl.dropbox.com/s/jq074rlcj9q3802/000020.jpg">

フォームを作成していきます。
HTMLとIonicコンポーネントを使っていきます。
### 見た目
#### contact.page.html
まず見た目だけ作ります。

```contact.page.html
<ion-header>
  <ion-toolbar>
    <ion-title>contact</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div id="container">
    <strong>お問い合わせ</strong>
    <form id="form">
      <p>お名前</p>
      <ion-item lines="full">
        <ion-input type="text" placeholder="お名前"></ion-input>
      </ion-item>
      <span class="error">
        お名前は必須です。
      </span>
      <p>Email</p>
      <ion-item lines="full">
        <ion-input type="email" placeholder="Email"></ion-input>
      </ion-item>
      <span class="error">
        emailは必須かつemailの形式で入力してください。
      </span>
      <p>お問い合わせ内容</p>
      <ion-item>
        <ion-textarea placeholder="お問い合わせ内容"></ion-textarea>
      </ion-item>
      <span class="error">
        お問い合わせ内容は必須かつ1文字以上10文字以下で入力してください。
      </span>
      <ion-button type="submit">送信</ion-button>
    </form>
  </div>
</ion-content>
```

#### contact.page.scss

```contact.page.scss
ion-content {
    --ion-background-color: white;
}

ion-item {
    --background: rgb(204, 204, 204);
    color: black;
}

ion-input {
    color: black;
}

#container {
    padding-top: 1em;
    padding-left: 3em;
    padding-right: 3em;
    display: flex;
    background-color: white;
    align-items: center;
    justify-content: center;
    flex-direction: column;
}

#container strong {
    color: black;
}

#container p {
    color: black;
    text-align: left;
    width: 100%;
}

#container ion-textarea {
    min-height: 80px;
}

#container ion-button {
    color: black;
    width: 100%;
    --border-radius: 0;
    --background: rgb(204, 204, 204);
    margin-top: 20px;
}

#form {
    width: 100%;
}

.error {
    color: red;
}
```
### 確認
タブがないので実機やエミュレータでは遷移ができず問い合わせページがまだ見れないため`ionic serve`で起動してhttp://localhost:8100/contact を確認します。
このような感じになっています。
<img width=300 src="https://dl.dropbox.com/s/1wy5ruxb4kahe36/000030.jpg" alt="000030.jpg">

### 機能
まず、フォームをグループとして管理できるようにionicFormという名前をつけます。

```diff_html:contact.page.html
- <form id="form">
+ <form id="form" [formGroup]="ionicForm">
```

バリデーショーンはReactiveFormsModuleとFormsModuleを使います。
contact.moudle.tsを開いてReactiveFormsModuleを追加します。

```diff_typescript:contact.module.ts
  import { NgModule } from '@angular/core';
  import { CommonModule } from '@angular/common';
- import { FormsModule } from '@angular/forms';
+ import { FormsModule, ReactiveFormsModule } from '@angular/forms';

  import { IonicModule } from '@ionic/angular';

  import { ContactPageRoutingModule } from './contact-routing.module';

  import { ContactPage } from './contact.page';

  @NgModule({
    imports: [
      CommonModule,
      FormsModule,
      IonicModule,
      ContactPageRoutingModule,
+     ReactiveFormsModule
    ],
    declarations: [ContactPage]
  })
  export class ContactPageModule {}

```
contact.page.tsを開いてFormGroup, FormBuilder, Validatorsを追加します。

コンポーネントの中でFormBuilderクラスの機能を利用することができようにするため、コンストラクター引数としてFormBuilderクラスのインスタンスを渡します。
ngOnInit()（コンポーネントが初期化されたときに呼び出される）にモジュールを使ってバリデーションを作成します。

```diff_typescript:contact.page.ts
  import { Component, OnInit } from '@angular/core';
+ import { FormGroup, FormBuilder, Validators } from '@angular/forms';

  @Component({
    selector: 'app-contact',
    templateUrl: './contact.page.html',
    styleUrls: ['./contact.page.scss'],
  })
  export class ContactPage implements OnInit {
+   ionicForm: FormGroup;
-   constructor() { }
+   constructor(public formBuilder: FormBuilder) { }


    ngOnInit() {
+     this.ionicForm = this.formBuilder.group({
+       name: ['', [Validators.required]],
+       email: [
+         '',
+         [
+           Validators.required,
+           Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,3}$'),
+         ],
+       ],
+       content: [
+         '',
+         [
+           Validators.required,
+           Validators.minLength(1),
+           Validators.maxLength(10),
+         ],
+       ],
+     });
    }

  }

```

```typescript:コピペ用
this.ionicForm = this.formBuilder.group({
  name: ['', [Validators.required]],
  email: [
    '',
    [
      Validators.required,
      Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,3}$'),
    ],
  ],
  content: [
    '',
    [
      Validators.required,
      Validators.minLength(1),
      Validators.maxLength(10),
    ],
  ],
});
```

すると、`ionicForm`の部分でvariableのinitの注意が出ます。tsconfig.jsonを開いて注意を消す設定をします。

```diff_json:tsconfig.json
  "compilerOptions": {
+     "strictPropertyInitialization": false,

```

htmlに戻ってcontrollerNameを追加していきます。

```diff_html:contact.page.html
  <ion-item lines="full">
-   <ion-input type="text" placeholder="お名前"></ion-input>
+   <ion-input formControlName="name" type="text" placeholder="お名前"></ion-input>
  </ion-item>
  <span class="error">
    お名前は必須です。
  </span>
  <p>Email</p>
  <ion-item lines="full">
-   <ion-input type="email" placeholder="Email"></ion-input>
+   <ion-input formControlName="email" type="email" placeholder="Email"></ion-input>
  </ion-item>
  <span class="error">
    emailは必須かつemailの形式で入力してください。
  </span>
  <p>お問い合わせ内容</p>
  <ion-item>
-   <ion-textarea  placeholder="お問い合わせ内容"></ion-textarea>
+   <ion-textarea formControlName="content" placeholder="お問い合わせ内容"></ion-textarea>
  </ion-item>
```
Anglurの構文を使ってバリデーションメッセージの表示・非表示を動的にできるようにします。
またformタグにサブミットハンドラーを追加します。


```diff_html:contact.page.html
  <ion-header>
    <ion-toolbar>
      <ion-title>contact</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-content>
    <div id="container">
      <strong>お問い合わせ</strong>
-     <form id="form" [formGroup]="ionicForm">
+     <form id="form" [formGroup]="ionicForm" (ngSubmit)="submitForm()">
        <p>お名前</p>
        <ion-item lines="full">
          <ion-input formControlName="name" type="text" placeholder="お名前"></ion-input>
        </ion-item>
-       <span class="error">
+       <span class="error" *ngIf="isSubmitted && errorControl['name'].errors?.['required']">
          お名前は必須です。
        </span>
        <p>Email</p>
        <ion-item lines="full">
          <ion-input formControlName="email" type="email" placeholder="Email"></ion-input>
        </ion-item>
-       <span class="error">
+       <span class="error" *ngIf="isSubmitted && (errorControl['email'].errors?.['required'] ||errorControl['email'].errors?.['pattern'])">
          emailは必須かつemailの形式で入力してください。
        </span>
        <p>お問い合わせ内容</p>
        <ion-item>
          <ion-textarea formControlName="content" placeholder="お問い合わせ内容"></ion-textarea>
        </ion-item>
-       <span class="error">
+       <span class="error" *ngIf="isSubmitted && (errorControl['content'].errors?.['required'] ||errorControl['content'].errors?.['min'] ||errorControl['content'].errors?.['max']  )">
          お問い合わせ内容は必須かつ1文字以上10文字以下で入力してください。
        </span>
        <ion-button type="submit">送信</ion-button>
      </form>
    </div>
  </ion-content>

```

```html:コピペ用
    <form id="form" [formGroup]="ionicForm" (ngSubmit)="submitForm()">
      <p>お名前</p>
      <ion-item lines="full">
        <ion-input formControlName="name" type="text" placeholder="お名前"></ion-input>
      </ion-item>
      <span class="error" *ngIf="isSubmitted && errorControl['name'].errors?.['required']">
        お名前は必須です。
      </span>
      <p>Email</p>
      <ion-item lines="full">
        <ion-input formControlName="email" type="email" placeholder="Email"></ion-input>
      </ion-item>
      <span class="error"
        *ngIf="isSubmitted && (errorControl['email'].errors?.['required'] ||errorControl['email'].errors?.['pattern'])">
        emailは必須かつemailの形式で入力してください。
      </span>
      <p>お問い合わせ内容</p>
      <ion-item>
        <ion-textarea formControlName="content" placeholder="お問い合わせ内容"></ion-textarea>
      </ion-item>
      <span class="error" *ngIf="isSubmitted && (errorControl['content'].errors?.['required'] ||errorControl['content'].errors?.['min'] ||errorControl['content'].errors?.['max']  )">文字以下で入力してください。
      </span>
      <ion-button type="submit">送信</ion-button>
    </form>
```


SubmitForm()を作成し、制御していきます。
一旦、バリデーションがすべてスルーだったらコンソールに値が表示されるようにします。
またget errorControl()を使って、Angulerの特定のフォームコントロールエラーメッセージを返します。
isSubmittedは初期値はfalseにし、送信が押されたら、trueとなるようにしています。

```diff_typescript:contact.page.ts
  import { Component, OnInit } from '@angular/core';
  import { FormGroup, FormBuilder, Validators } from '@angular/forms';

  @Component({
    selector: 'app-contact',
    templateUrl: './contact.page.html',
    styleUrls: ['./contact.page.scss'],
  })
  export class ContactPage implements OnInit {
+   isSubmitted = false;
    ionicForm: FormGroup;

    constructor(public formBuilder: FormBuilder) { }


    ngOnInit() {
      this.ionicForm = this.formBuilder.group({
        name: ['', [Validators.required]],
        email: [
          '',
          [
            Validators.required,
            Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,3}$'),
          ],
        ],
        content: [
          '',
          [
            Validators.required,
            Validators.minLength(1),
            Validators.maxLength(10),
          ],
        ],
      });
    }

+   submitForm() {
+     this.isSubmitted = true;
+     if (!this.ionicForm.valid) {
+       return;
+     }
+     let data = this.ionicForm.value;
+     console.log(data);
+   }

+   get errorControl() {
+     return this.ionicForm.controls;
+   }
  }

```
```typescript:コピペ用
submitForm() {
    this.isSubmitted = true;
    if (!this.ionicForm.valid) {
      return;
    }
    let data = this.ionicForm.value;
    console.log(data);
  }

  get errorControl() {
    return this.ionicForm.controls;
  }
```

`ionic serve`で確認し、バリデーションが想定どおり動いていて、
<img width=300 alt="000050.jpg" src="https://dl.dropbox.com/s/gvydu5jyefon2ek/000050.jpg" />
適切な値が入った状態でボタンクリックするとコンソールに値が表示されていることを確認します。
<img width=300 src="https://dl.dropbox.com/s/imx0jkcba758tbd/000040.jpg" alt="000040.jpg">

# API連携
`console.log(data)`を削除してAPI連携のコードを追加します。

```diff_typescript:contact.page.ts
   submitForm() {
      this.isSubmitted = true;
      if (!this.ionicForm.valid) {
        return;
      }
      let data = this.ionicForm.value;
+     const api_url = 'apiのURL';
+     fetch(api_url, {
+       method: 'post',
+       headers: {
+         'Content-Type': 'application/x-www-form-urlencoded',
+       },
+       body: encodeURI(
+         `name=${data.name}&email=${data.email}&body=${data.body}`
+       ),
+     })
+       .then((response) => response.json())
+       .then((result) => alert(result.message))
+       .catch((error) => alert(error.message));
    }
```
```typescript:コピペ用
const api_url = 'apiのURL';
fetch(api_url, {
 method: 'post',
 headers: {
   'Content-Type': 'application/x-www-form-urlencoded',
 },
 body: encodeURI(
   `name=${data.name}&email=${data.email}&body=${data.body}`
 ),
})
 .then((response) => response.json())
 .then((result) => alert(result.message))
 .catch((error) => alert(error.message));
```

## 確認
きちんとmessageが返ってきています。
<img width=300 alt="000060.jpg" src="https://dl.dropbox.com/s/9c28nmkj0t2ywbd/000060.jpg" />


# 関連コンテンツ
* 次：[Ionicで簡易コーポレートサイト（アプリ）を作成する #3 -タブバー編-](https://qiita.com/minimumskills/private/61d3c42ef761a309a6fc)
* 前：[Ionicで簡易コーポレートサイト（アプリ）を作成する #1 -TOP画面編-](https://qiita.com/minimumskills/private/b052bcaecfc593225432)
* [超シンプルなWeb（アプリ）作成を通して各種フロントエンド技術を体験する - 2023 Early -](https://qiita.com/minimumskills/private/9e7efe86e244bb9844b0)