はじめに
アカウント登録画面でよく見る姓と名の入力欄について、フォームが二つに別れているパターンと、フォームが一つになっていてスペースで姓と名を区切るパターンの二つがあると思います。
今回は後者を採用した場合に必要になるバリデーションについて書きます(なぜなら今作ってるシステムがそうなので)。
とはいえ、他のWebサービスを見ると基本的に姓と名でフォームを分けてる場合がほとんどですね。なんでフォームを一つにしちゃったんだろう。。。デザインの問題?画面が縦に長すぎるとまずいとか
バリデーション内容
氏名についてのバリデーションを設定します。チェックする項目は下記とします。
- 氏名に少なくとも1つのスペース(半角もしくは全角)が入力されていること
- ただし、スペースが文頭または文末に入力されている場合は、未入力の扱いとする
- 複数スペースが入力された場合は、最初のスペースを姓名の区切りとして利用する
バージョンなどの情報
JavaScriptのフレームワークとしてはAngularを使用していますが、他フレームワークや生JavaScriptでもバリデーションのロジック部分は変わらないと思います。
- Angular@7.0.6
- typescript@3.1.6
- webpack@4.19.1
- Angular Material@7.0.4
実装
CSSフレームワークとして、Angular Material を使用しています。Angular Material自体のインストール方法や使い方は下記の記事を参照してください。
Angular Material の Tableを使う - 中安拓也のブログ
Angular Materialでログインフォームを作る - 中安拓也のブログ
Angularのバリデーション機能は、テンプレート(HTML)に検証ルールを記述するテンプレート駆動型と、コンポーネント側に検証ルールを記述するモデル駆動型に分かれていますが、本記事ではモデル駆動型の方を使用しています。
まず姓と名の間にスペースが入力されているかを判定するカスタムバリデーションを作成します。
custom-validator.ts
import { ValidationErrors, FormControl } from '@angular/forms'; export class CustomValidator { static haveBlank(control: FormControl): ValidationErrors | null { const value = (control.value || '') + ''; // 両端のスペースを取り除く const name = value.trim(); // 半角スペース const NAME_COLUMN_SPRIT_VALUE = ' '; // 全角スペース const NAME_COLUMN_SPRIT_VALUE_W = ' '; let isError = false; if (name.indexOf(NAME_COLUMN_SPRIT_VALUE) < 0 && name.indexOf(NAME_COLUMN_SPRIT_VALUE_W) < 0) { // 半角スペースも全角スペースも含まれていない場合 isError = true; } // 姓と名の間にスペースが入力されていない場合は、バリデーションエラーを返す return isError ? { haveBlank: true } : null; } }
実装内容としては、文字列の両端のスペースを取り除いた後に、半角または全角のスペースが入力されているかどうかをindexOf()
メソッドを使用してチェックしています。(indexOf()
メソッドの代わりにincludes()
メソッドを使ってもOKです)
続いて、作成したカスタムバリデーションをフォームの方に設定してあげると、実装が完了します。
app.component.ts
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; import { CustomValidator } from './custom-validator'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { public nameRegisterForm: FormGroup; public nameControl: FormControl; constructor(private builder: FormBuilder) { this.createForm(); } public ngOnInit(): void { this.nameControl = this.nameRegisterForm.get('email') as FormControl; } public onSubmit() { console.log(); } /** * フォームグループの初期化を実行する * * @private * @memberof AppComponent */ private createForm() { // 氏名欄のバリデーションを設定している this.nameRegisterForm = this.builder.group({ name: ['', [Validators.required, CustomValidator.haveBlank]] }); } }
app.component.html
<div class="container"> <mat-card class="login-card"> <mat-card-header> <mat-card-title class="login-title">アカウント登録</mat-card-title> </mat-card-header> <mat-card-content> <form [formGroup]="nameRegisterForm" (ngSubmit)="onSubmit()" class="login-form"> <mat-form-field class="input-field"> <!-- 氏名の入力フォーム --> <input matInput placeholder="氏名(姓と名の間にスペースを入力してください)" id="name" name="name" [formControl]="nameRegisterForm.controls.name" required> <!-- 必須入力のエラーメッセージ --> <mat-error *ngIf="nameRegisterForm.controls.name.errors?.required">氏名は必須入力です</mat-error> <!-- スペースがない場合のエラーメッセージ --> <mat-error *ngIf="nameRegisterForm.controls.name.errors?.haveBlank">姓と名の間にはスペースを入力してください</mat-error> </mat-form-field> <!-- 登録ボタン --> <button type="submit" class="register-button" mat-raised-button color="primary" [disabled]="nameRegisterForm.invalid">登録</button> </form> </mat-card-content> </mat-card> </div>
スペースが入力されなかった場合のエラーメッセージについては、テンプレート(HTML)の方に記載しています。
動作確認
実際に動かしてみます。
姓と名の間にスペースが入力されていない氏名を入力すると、ちゃんとエラーメッセージを表示してくれます。
姓と名の間にちゃんとスペースを入力してあげると、エラーメッセージが消えて登録ボタンもアクティブになりました。成功ですね
おわりに
Angularのカスタムバリデーションですが、他にもディレクティブ機能を使う方法などがあり、また今回の例だとそもそもカスタムバリデーションを作らずに正規表現で解決する方法もありました。実際のシステム開発だと姓と名それぞれの文字数制限などのバリデーションが追加されるであろうことを考えると、ディレクティブを使ったり正規表現を使ったほうがキレイなコードが書けたのでは.....みたいな気持ちがあります。でも次回への課題ということでこの記事自体は終了します。