Angularの基礎(入力画面)
Angularを使った入力画面の基本要素をまとめます。
基本要素は2点です。
- 双方向データバインディング
- バリデーション
今回のサンプルは、Angular Materialを使っていますが、基本要素2点はAngular Materialに依存したものではありません。
準備作業
準備作業として、モデルクラスとコンポーネントクラスを作成します。
/src/app/model/project.model.ts
/src/app/project/new/new.component.ts
/src/app/project/new/new.component.html
それぞれのソースコードは以下の通り。
export class Project {
constructor(public uid: number | null, public code: string, public name: string) {}
}
import { Component, EventEmitter, Output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
@Component({
selector: 'app-project-new',
standalone: true,
imports: [MatButtonModule, MatFormFieldModule, MatInputModule],
templateUrl: './new.component.html',
styleUrl: './new.component.css'
})
export class ProjectNewComponent {
@Output() setNewFlg: EventEmitter<boolean> = new EventEmitter();
constructor() {}
clickClose = () => {
this.setNewFlg.emit(false);
}
}
<div class="overlay">
<div id="form">
<header>
<h1>プロジェクト新規登録</h1>
</header>
<div id="button-area">
<button mat-flat-button color="accent">Submit</button>
<button mat-flat-button color="primary" (click)="clickClose()">Cancel</button>
</div>
<div id="entry-form">
<mat-form-field>
<mat-label>Code</mat-label>
<input matInput type="text">
</mat-form-field>
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput type="text">
</mat-form-field>
</div>
</div>
</div>
実際の画面はこのような感じです。
モーダル画面仕様で作成したため、Cancelボタンに関連するロジックも含まれていますが、今回は無視してください。
双方向データバインディング
これは変数と画面項目を紐付けてしまう方法です。
利点としては、変数へセットする処理を記述しなくても良くなるというところでしょうか。
今回は、モデルクラスで変数を定義して、それを入力項目と紐付けます。
TSファイル
import { Component, EventEmitter, Output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { Project } from '../../model/project.model';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-project-new',
standalone: true,
imports: [MatButtonModule, MatFormFieldModule, MatInputModule, FormsModule],
templateUrl: './new.component.html',
styleUrl: './new.component.css'
})
export class ProjectNewComponent {
@Output() setNewFlg: EventEmitter<boolean> = new EventEmitter();
// 入力値格納変数
model: Project = new Project(null, "", "");
constructor() {}
clickClose = () => {
this.setNewFlg.emit(false);
}
}
FormsModuleクラスをインポートし、Projectモデルクラスで変数定義しています。
HTMLファイル
<div class="overlay">
<div id="form">
<header>
<h1>プロジェクト新規登録</h1>
</header>
<div id="button-area">
<button mat-flat-button color="accent">Submit</button>
<button mat-flat-button color="primary" (click)="clickClose()">Cancel</button>
</div>
<div id="entry-form">
<mat-form-field>
<mat-label>Code</mat-label>
<input matInput type="text" [(ngModel)]="model.code">
</mat-form-field>
<div>{{model.code}}</div>
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput type="text" [(ngModel)]="model.name">
</mat-form-field>
<div>{{model.name}}</div>
</div>
</div>
</div>
それぞれの入力項目に [(ngModel)]="model.xxxx" を追記しています。
ngModelディレクティブは、FormsModuleクラスで使えるようになります。
それぞれmodelオブジェクト変数の各要素と紐付けする記述としています。
また、項目への入力がわかるように、divタグでそれぞれの変数値を表示するようにしています。
実行結果
実際に項目に入力した時の表示は以下の感じです。
入力に同期して表示されました。
TSファイル変更(初期値設定)
import { Component, EventEmitter, Output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { Project } from '../../model/project.model';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-project-new',
standalone: true,
imports: [MatButtonModule, MatFormFieldModule, MatInputModule, FormsModule],
templateUrl: './new.component.html',
styleUrl: './new.component.css'
})
export class ProjectNewComponent {
@Output() setNewFlg: EventEmitter<boolean> = new EventEmitter();
// 入力値格納変数
model: Project = new Project(null, "コード初期値", "名前初期値");
constructor() {}
clickClose = () => {
this.setNewFlg.emit(false);
}
}
modelオブジェクト変数の初期値を設定するように変更しました。
実行結果
これで画面をリフレッシュすると以下のようになります。
入力項目に初期値がセットされました。
これが双方向データバインディングです。
バリデーション
続けてバリデーションを記述していきます。
Code項目には、必須と半角英数のみというバリデーションを、Name項目には必須のバリデーションをつけます。
TSファイル
import { Component, EventEmitter, Output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { Project } from '../../model/project.model';
import { FormsModule, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-project-new',
standalone: true,
imports: [MatButtonModule, MatFormFieldModule, MatInputModule, FormsModule, ReactiveFormsModule],
templateUrl: './new.component.html',
styleUrl: './new.component.css'
})
export class ProjectNewComponent {
@Output() setNewFlg: EventEmitter<boolean> = new EventEmitter();
// 入力値格納変数
model: Project = new Project(null, "", "");
// バリデーション
codeFormControl = new FormControl('', [Validators.required, Validators.pattern('/^[0-9a-zA-Z]+$/')]);
nameFormControl = new FormControl('', [Validators.required]);
constructor() {}
clickClose = () => {
this.setNewFlg.emit(false);
}
}
新たに FormControl, Validators, ReactiveFormsModule の3つのクラスをインポートしています。
また、バリデーション用にFormControlインスタンスを作っています。
1つはcode用として required と pattern で正規表現(半角英数)を設定、もう1つはname用で required を設定。
先ほど、初期値設定した部分は戻してあります。
HTMLファイル
<div class="overlay">
<div id="form">
<header>
<h1>プロジェクト新規登録</h1>
</header>
<div id="button-area">
<button mat-flat-button color="accent">Submit</button>
<button mat-flat-button color="primary" (click)="clickClose()">Cancel</button>
</div>
<div id="entry-form">
<mat-form-field>
<mat-label>Code</mat-label>
<input matInput type="text"
[(ngModel)]="model.code" [formControl]="codeFormControl">
</mat-form-field>
<div>{{model.code}}</div>
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput type="text"
[(ngModel)]="model.name" [formControl]="nameFormControl">
</mat-form-field>
<div>{{model.name}}</div>
</div>
</div>
</div>
それぞれの項目に [formControl]="xxxxFormControl" 属性を追記しています。
formControlディレクティブは ReactiveFormsModuleクラスで使えるようになっています。
属性値には、TSファイルで定義した FormControlインスタンスを設定しています。
実行結果
実際に画面操作した動きです。(codeには全角入力、nameは未入力にしてフォーカスアウトしてみました)
いずれもエラー表示(赤字)になりました。
もう少しエラーをわかりやすくします
<div class="overlay">
<div id="form">
<header>
<h1>プロジェクト新規登録</h1>
</header>
<div id="button-area">
<button mat-flat-button color="accent">Submit</button>
<button mat-flat-button color="primary" (click)="clickClose()">Cancel</button>
</div>
<div id="entry-form">
<mat-form-field>
<mat-label>Code</mat-label>
<input matInput type="text"
[(ngModel)]="model.code" [formControl]="codeFormControl">
@if (!codeFormControl.hasError('required') && codeFormControl.hasError('pattern')) {
<mat-error>半角英数のみで入力してください</mat-error>
}
@if (codeFormControl.hasError('required')) {
<mat-error>必須入力項目です</mat-error>
}
</mat-form-field>
<div>{{model.code}}</div>
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput type="text"
[(ngModel)]="model.name" [formControl]="nameFormControl">
@if (nameFormControl.hasError('required')) {
<mat-error>必須入力項目です</mat-error>
}
</mat-form-field>
<div>{{model.name}}</div>
</div>
</div>
</div>
それぞれの項目の後に、エラー時の表示条件を追記しています。(それぞれのFormControlインスタンスで判断可能です)
以下のように表示されるようになります。
まとめ
入力画面では、よく使うであろう動作面をまとめてみました。
こういう基本的なことを1つ1つ理解していって、ようやく画面が出来上がるのだと改めて実感します。