見出し画像

Angular Formの実装

今回は僕のプロジェクトのFormの実装についてのお話をします。

僕の求人webアプリではユーザーのプロフィール、企業側のプロフィール、求人投稿の3つのフォームがあります。

全部あげるとコード量が多くなってしますので今回はにユーザーのプロフィールの完成形のコードだけにします。

・ngSubmit... Formを送信するに使います。

・mat-error... Formのエラーメッセージを表示したり際に使用します。Formに関しては必須項目です。

・ngIf... 条件分岐の際に使います。Javascriptで言うif文と同じです。このフォームでの使い方は条件に満たしていなかったらバリデーションがかかるようになっています。

<mat-error *ngIf="nameControl.hasError('required')"
       >必須入力です</mat-error
     >

何も入力されていなかったら必須入力ですよと言うエラーメッセージを表示しています。他にも最大・最小文字数制限、正規表現などたくさんバリデーションの種類があります。

・ngFor... ループ文ですJavascriptで言うFor文と同じものです。僕のフォームではmat-selectで生年月日を入力、学歴選択場面のときに使用しています。

//html//
<form [formGroup]="form" (ngSubmit)="submit()" class="profile">
 <h2>プロフィール設定</h2>
 <div>
   <input type="file" (change)="setAvatar($event)" />
 </div>
 <div>
   <mat-form-field class="profile__field">
     <mat-label>氏名</mat-label>
     <input
       matInput
       placeholder="名前"
       name="name"
       formControlName="name"
       autocomplete="name"
       required
     />
     <mat-error *ngIf="nameControl.hasError('required')"
       >必須入力です</mat-error
     >
   </mat-form-field>
 </div>

 <div>
   <mat-form-field class="profile__field">
     <input
       matInput
       placeholder="住所"
       name="address"
       formControlName="address"
       autocomplete="street-address"
       required
     />
     <mat-error *ngIf="addressControl.hasError('required')"
       >必須入力です</mat-error
     >
   </mat-form-field>
 </div>
 <div formGroupName="bday">
   <mat-form-field class="profile__field">
     <mat-select placeholder="年" formControlName="year" required>
       <mat-option
         [value]="bottom"
         required
         *ngFor="let year of years; let i = index"
         >{{ i + bottom }}年</mat-option
       >
     </mat-select>
     <mat-error *ngIf="yearControl.hasError('required')"
       >必須入力です</mat-error
     >
   </mat-form-field>

   <mat-form-field class="profile__field">
     <mat-select placeholder="月" formControlName="month" required>
       <mat-option [value]="i" *ngFor="let month of months; let i = index"
         >{{ i + 1 }}月</mat-option
       >
     </mat-select>
     <mat-error *ngIf="monthControl.hasError('required')"
       >必須入力です</mat-error
     >
   </mat-form-field>

   <mat-form-field class="profile__field">
     <mat-select placeholder="日" formControlName="day" required>
       <mat-option [value]="i" *ngFor="let day of days; let i = index"
         >{{ i + 1 }}日</mat-option
       >
     </mat-select>
     <mat-error *ngIf="dayControl.hasError('required')"
       >必須入力です</mat-error
     >
   </mat-form-field>
 </div>

 <div class="profile__field">
   <mat-label>性別:</mat-label>
   <mat-radio-group formControlName="gender" required>
     <mat-radio-button value="male">男性</mat-radio-button>
     <mat-radio-button value="female">女性</mat-radio-button>
   </mat-radio-group>
 </div>

 <div>
   <mat-form-field class="profile__field">
     <input
       matInput
       placeholder="Email"
       formControlName="email"
       required
       autocomplete="email"
     />
     <mat-error *ngIf="emailControl.hasError('required')"
       >必須入力です</mat-error
     >
     <mat-error *ngIf="emailControl.hasError('email')"
       >メールアドレス形式ではありません。</mat-error
     >
   </mat-form-field>
 </div>

 <div>
   <mat-form-field class="profile__field">
     <input
       type="tel"
       matInput
       placeholder="電話番号"
       formControlName="tel"
       required
       autocomplete="tel"
     />
     <mat-error *ngIf="telControl.hasError('required')"
       >必須入力です</mat-error
     >
     <mat-error *ngIf="telControl.hasError('pattern')"
       >数字でハイフンなしでお願いします</mat-error
     >
   </mat-form-field>
 </div>

 <div>
   <mat-form-field class="profile__field">
     <textarea
       matInput
       placeholder="自己紹介"
       formControlName="introduce"
     ></textarea>
   </mat-form-field>
 </div>

 <div>
   <mat-form-field class="profile__field">
     <mat-select placeholder="学校区分" formControlName="school" required>
       <mat-option [value]="school" *ngFor="let school of schools">{{
         school
       }}</mat-option>
     </mat-select>
     <mat-error *ngIf="schoolControl.hasError('required')"
       >選択してください</mat-error
     >
   </mat-form-field>

   <mat-form-field class="profile__field">
     <input
       matInput
       placeholder="〇〇大学〇〇学部"
       formControlName="belongs"
       required
     />
     <mat-error *ngIf="addressControl.hasError('required')"
       >必須入力です</mat-error
     >
   </mat-form-field>

   <mat-form-field class="profile__field">
     <mat-select placeholder="卒業/在学/中退" formControlName="state" required>
       <mat-option
         [value]="state"
         *ngFor="let state of states; let i = index"
         >{{ state }}</mat-option
       >
     </mat-select>
     <mat-error *ngIf="stateControl.hasError('required')"
       >選択してください</mat-error
     >
   </mat-form-field>
 </div>

 <div class="profile__field">
   <mat-form-field class="profile__field">
     <input matInput placeholder="資格1" formControlName="tagOne" />
   </mat-form-field>

   <mat-form-field class="profile__field">
     <input matInput placeholder="資格2" formControlName="tagSecond" />
   </mat-form-field>
 </div>

 <div class="profile__field">
   <mat-form-field class="profile__field">
     <textarea
       matInput
       placeholder="勤務可能な日時"
       formControlName="possibleDay"
       required
     ></textarea>
     <mat-error *ngIf="possibleDayControl.hasError('required')"
       >必須入力です</mat-error
     >
   </mat-form-field>
 </div>
 <div>
   <button
     class="profile__btn"
     [disabled]="form.invalid || form.pristine"
     mat-raised-button
     color="primary"
   >
     プロフィール更新
   </button>
 </div>
</form>

Tsの方では

import { FormBuilder, Validators, FormControl } from '@angular/forms';

この3つのimportを忘れてはいけません。これらをimportし忘れると正常に動かないので必須です。

//ts//
import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators, FormControl } from '@angular/forms';
import { UserProfileService } from 'src/app/service/user-profile.service';
import { AuthService } from 'src/app/services/auth.service';
import { JobPostService } from 'src/app/service/job-post.service';
@Component({
 selector: 'app-profile',
 templateUrl: './profile.component.html',
 styleUrls: ['./profile.component.scss']
})
export class ProfileComponent implements OnInit {
 years = new Array(61).fill(null);
 bottom = new Date().getFullYear() - 60;
 months = new Array(12).fill(null);
 days = new Array(31).fill(null);

 schools = ['中学', '高校', '専門', '大学', '大学院'];
 states = ['卒業', '在学中', '中退'];

 image: File;

 form = this.fb.group({
   name: ['', [Validators.required]],
   address: ['', [Validators.required]],
   bday: this.fb.group({
     year: ['', [Validators.required]],
     month: ['', [Validators.required]],
     day: ['', [Validators.required]]
   }),
   gender: ['', [Validators.required]],
   email: ['', [Validators.required, Validators.email]],
   tel: ['', [Validators.required, Validators.pattern(/^0\d{9,10}$/)]],
   school: ['', [Validators.required]],
   state: ['', [Validators.required]],
   possibleDay: ['', [Validators.required]],
   tagOne: ['', []],
   tagSecond: ['', []],
   introduce: ['', []],
   belongs: ['', [Validators.required]]
 });
 userId: string;

 get nameControl() {
   return this.form.get('name') as FormControl;
 }

 get addressControl() {
   return this.form.get('address') as FormControl;
 }
 get yearControl() {
   return this.form.get('bday.year') as FormControl;
 }

 get monthControl() {
   return this.form.get('bday.month') as FormControl;
 }

 get dayControl() {
   return this.form.get('bday.day') as FormControl;
 }

 get genderControl() {
   return this.form.get('gender') as FormControl;
 }

 get emailControl() {
   return this.form.get('email') as FormControl;
 }

 get telControl() {
   return this.form.get('tel') as FormControl;
 }

 get schoolControl() {
   return this.form.get('school') as FormControl;
 }

 get stateControl() {
   return this.form.get('state') as FormControl;
 }

 get possibleDayControl() {
   return this.form.get('possibleDay') as FormControl;
 }

 get tagOneControl() {
   return this.form.get('tagOne') as FormControl;
 }

 get tagSecondControl() {
   return this.form.get('tagSecond') as FormControl;
 }

 get introduceControl() {
   return this.form.get('introduce') as FormControl;
 }

 get belongsControl() {
   return this.form.get('belongs') as FormControl;
 }

 constructor(
   private fb: FormBuilder,
   private userProfileService: UserProfileService,
   private authService: AuthService,
   private jobPostService: JobPostService
 ) {}

 ngOnInit() {
   this.userProfileService
     .getProfile(this.authService.uid)
     .subscribe(profile => {
       if (profile) {
         this.form.patchValue(profile);
       }
     });
 }

 setAvatar(event) {
   if (event.target.files.length) {
     const image = event.target.files[0];
     this.image = image;
   }
 }
 submit() {
   console.log(this.form.value);
   this.userProfileService.createUser(
     {
       userId: this.authService.uid,
       ...this.form.value
     },
     this.image
   );
 }
}

Angular Form実装においてのつまずいた点としては、

form = this.fb.group({
   name: ['', [Validators.required]],
   address: ['', [Validators.required]],
   bday: this.fb.group({
     year: ['', [Validators.required]],
     month: ['', [Validators.required]],
     day: ['', [Validators.required]]
   }),
   gender: ['', [Validators.required]],
   email: ['', [Validators.required, Validators.email]],
   tel: ['', [Validators.required, Validators.pattern(/^0\d{9,10}$/)]],
   school: ['', [Validators.required]],
   state: ['', [Validators.required]],
   possibleDay: ['', [Validators.required]],
   tagOne: ['', []],
   tagSecond: ['', []],
   introduce: ['', []],
   belongs: ['', [Validators.required]]
 });

Tsのこの部分のgroupの入れ子です。生年月日の部分を1つのグループにしたかったのですが、最初はそのやり方がわからずエラーの連続でした。生年月日のところはまだ完成ではないので月によって日にちの末端が変化する実装もしていこうと思うので完成したらその記事も載せていこうかなと思います。

あとは僕はタイプミスが多いので、error文のところでタイプミスをして二時間くらいは時間を無駄にしました。タイプミスをしないのも開発をスムーズに進めるために大切なことだなと痛感しました。規模が大きくなるともっと時間がかかると思うのでw

今回はFormの実装について書きました。違う記事でもFormのことを載せているのでよければそちらもご覧ください。最後までお付き合いいただきありがとうございます。