概要
- C#/WPF では INotifyDataErrorInfoとDataAnnotations を使用した入力バリデーションが存在します。今回はそれをAngular版にしてみました。
- 今回はclass-validator + Angular + TypeScriptで実現します。
INotifyDataErrorInfo実装サンプル
github.com
実装方針
- class-validatorのデコレータをプロパティに定義する。
- ベースとなるViewModel内でclass-validatorのvalidateSyncで入力バリデーションを行う。
- プロパティ変更時に上記のバリデーションが実行されるように実装する。
実行環境
- Node.js - 10.x
- Yarn - 1.17.x
使用ライブラリ
- TypeScript - 3.5.x
- Angular - 8.2.x
- class-validator - 0.10.x
view-models/notify-error-info.view-model.ts
import { validateSync } from 'class-validator';
export abstract class NotifyErrorInfoViewModel {
protected readonly errors = new Map<string, string[]>();
get hasErrors() {
return this.errors.size > 0;
}
getErrors(propertyName: string): string[] {
const errors = this.errors.get(propertyName);
return errors ? errors : [];
}
protected onPropertyChanged(propertyName: string): void {
this.setErrors(propertyName);
}
protected setErrors(propertyName: string): void {
const results = validateSync(this);
const errorMsgs = results.filter((r) => r.property === propertyName).map((r) => Object.values(r.constraints));
if (errorMsgs.length > 0) {
this.errors.set(propertyName, errorMsgs.reduce((prev, current) => prev.concat(current)));
} else {
this.errors.delete(propertyName);
}
}
}
view-models/edit-data.view-model.ts
import { MaxLength, Max, Min, IsNotEmpty } from 'class-validator';
import { NotifyErrorInfoViewModel } from './notify-error-info.view-model';
export class EditData extends NotifyErrorInfoViewModel {
@MaxLength(20, { message: 'name は 20文字以内で入力してください。' })
@IsNotEmpty({ message: 'name を入力してください。' })
set name(value: string) {
this._name = value;
this.onPropertyChanged('name');
}
get name() {
return this._name;
}
@Max(99, { message: 'age は 10~99で入力してください。' })
@Min(10, { message: 'age は 10~99で入力してください。' })
set age(value: number) {
this._age = value;
this.onPropertyChanged('age');
}
get age() {
return this._age;
}
private _name = '';
private _age = 20;
}
app.component.ts
import { Component } from '@angular/core';
import { EditData } from './view-models/edit-data.view-model';
@Component({
selector: 'app-root',
template: `
<div>
Name: <input name="name" type="text" [(ngModel)]="editData.name" />
<span style="color: red;" *ngIf="editData.getErrors('name').length > 0">
{{ editData.getErrors('name')[0] }}
</span>
</div>
<div>
Age: <input name="age" type="number" [(ngModel)]="editData.age" />
<span style="color: red;" *ngIf="editData.getErrors('age').length > 0">
{{ editData.getErrors('age')[0] }}
</span>
</div>
<div>hasError: {{ editData.hasErrors }}</div>
`,
})
export class AppComponent {
editData = new EditData();
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, FormsModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
実行結果
github.com
おわりに
- 多言語でよくあるメタデータベースでのバリデーションがTypeScriptでも実現できました。
- Angular/TypeScriptでonPropertyChangedパターンで実装すると冗長になるので、もうちょいシンプルな記載にしたいですね。