概要
AngularでのFormのカスタムバリデーションには主に以下の方法があります。
- Directiveで用意する
- ビルトインのValidatorsのような関数を用意する
今回は後者の実装例を紹介します。
validationロジック
今回はクレジットカードの簡易チェックをするvalidatorを導入します。
クレジットカードの番号はLuhnアルゴリズムに基づいています。
これによってわざわざサーバ通信を挟む前に、入力ミスなどによる不正な番号を弾くことができます。
環境
- Angular 2.4.5
- angular-cli 1.0.0-beta.28.3
成果物
今回の完成形は以下
実装
import { AbstractControl } from '@angular/forms'; export class CreditCardValidator { static luhn(c: AbstractControl) { const num = c.value; if (num.length < 13) { return {luhn: true}; } const digits = num.split('').reverse(); let digit; let sum = 0; for (let i = 0; i < digits.length; i++) { digit = digits[i]; digit = parseInt(digit, 10); // if odd, multiplied by 2. if ((i + 1) % 2 === 0) { digit *= 2; } // if more than 10, subtract 9. if (digit > 9) { digit -= 9; } sum += digit; } if (sum % 10 !== 0) { return {luhn: true}; } return null; } }
ロジックはLuhnアルゴリズムの通りなので、それ以外の部分についてのポイントを指摘します。
AbstractControl
を引数にとる- 成功時は
null
を返す - 失敗時は
{エラー名: true}
の形で返す
3つ目ですが、これはAbstractControl
がhasError('エラー名')
というメソッドを持つので、それを使ってバリデーションのエラーを細かく判別しやすくなるためです。
テストコード
次にテストコードの書き方を紹介します。
このCreditCardValidator
をnewしてテストするよりも、テスト用のComponentを用意してそこで実際にValidatorとして扱った方がテストしやすいです。
TestComponent
@Component({ template: ` <form [formGroup]="fg"> <input type="text" formControlName="card"> </form> ` }) class TestComponent implements OnInit { public fg: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.fg = this.fb.group({ 'card': ['', CreditCardValidator.luhn], }); } }
こんな感じのテスト用のComponentを用意します。以前紹介したModel Drivenのフォームですね。
これにビルトインのValidatorsのようにCreditCardValidator.luhn
をセットします。
テストスイート(describe)
次にテストの前準備の部分を書きます。
describe('Credit card validators', () => { let component: TestComponent; let fixture: ComponentFixture<TestComponent>; let card: AbstractControl; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ TestComponent ], imports: [ ReactiveFormsModule ], }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; fixture.detectChanges(); card = component.fg.controls['card']; }); });
ポイントは以下です。
- 先程の
TestComponent
をdeclarationsに入れる ReactiveFormsModule
をimportしているcomponent.fg.controls['card']
で要素を取得
テストケース
これを元に以下のようにテストケースを書いていきます。
it('should be form invalid when empty', () => { expect(component.fg.valid).toBeFalsy(); }); it('should be card field invalid when empty', () => { expect(card.valid).toBeFalsy(); }); it('should be errors field invalid when empty', () => { expect(card.hasError('luhn')).toBeTruthy(); }); it('should be errors field invalid when number is short', () => { card.setValue('42424242'); expect(card.hasError('luhn')).toBeTruthy(); }); it('should be errors field invalid when number is invalid', () => { card.setValue('4242424242424241'); expect(card.hasError('luhn')).toBeTruthy(); }); it('should be valid', () => { card.setValue('4242424242424242'); expect(card.valid).toBeTruthy(); });
ポイントは以下
- FormGroup(fg)の
valid
、要素(card)のvalid
でバリデーションチェック hasError()
で詳細なエラーを判別setValue()
で検証する値をセット
まとめ
カスタムバリデーションの実装例を紹介しました。
1つ作り方を覚えればメールのフォーマットチェックなど、色んなチェックをすることができるようになります。
またViewと違い簡単にテストも書けるので、こういったロジックメインの部分はなるべくテストを書いてバグを減らしていきたいですね。