diff --git a/modules/@angular/forms/src/validators.ts b/modules/@angular/forms/src/validators.ts index b1647d3f33..a56dc0974d 100644 --- a/modules/@angular/forms/src/validators.ts +++ b/modules/@angular/forms/src/validators.ts @@ -12,7 +12,7 @@ import {toPromise} from 'rxjs/operator/toPromise'; import {AsyncValidatorFn, Validator, ValidatorFn} from './directives/validators'; import {StringMapWrapper} from './facade/collection'; import {isPresent} from './facade/lang'; -import {AbstractControl} from './model'; +import {AbstractControl, FormControl, FormGroup} from './model'; import {isPromise} from './private_import_core'; function isEmptyInputValue(value: any): boolean { @@ -63,6 +63,30 @@ const EMAIL_REGEXP = * @stable */ export class Validators { + /** + * Validator that compares the value of the given FormControls + */ + static equalsTo(...fieldPaths: string[]): ValidatorFn { + return function(control: FormControl): {[key: string]: any} { + if (fieldPaths.length < 1) { + throw new Error('You must compare to at least 1 other field'); + } + + for (let fieldName of fieldPaths) { + let field = (control.parent).get(fieldName); + if (!field) { + throw new Error( + `Field: ${fieldName} undefined, are you sure that ${fieldName} exists in the group`); + } + + if (field.value !== control.value) { + return {'equalsTo': {'unequalField': fieldName}}; + } + } + return null; + }; + } + /** * Validator that requires controls to have a non-empty value. */ diff --git a/modules/@angular/forms/test/validators_spec.ts b/modules/@angular/forms/test/validators_spec.ts index 6b9d18af36..62e097b198 100644 --- a/modules/@angular/forms/test/validators_spec.ts +++ b/modules/@angular/forms/test/validators_spec.ts @@ -8,7 +8,7 @@ import {fakeAsync, tick} from '@angular/core/testing'; import {describe, expect, it} from '@angular/core/testing/testing_internal'; -import {AbstractControl, FormArray, FormControl, Validators} from '@angular/forms'; +import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@angular/forms'; import {Observable} from 'rxjs/Observable'; import {normalizeAsyncValidator} from '../src/directives/normalize_validator'; @@ -36,6 +36,44 @@ export function main() { } describe('Validators', () => { + describe('equalsTo', () => { + it('should not error when equal', () => { + let group = new FormGroup({f1: new FormControl('a'), f2: new FormControl('a')}); + let validator = Validators.equalsTo('f2'); + expect(validator(group.controls['f1'])).toBeNull(); + }); + + it('should error when not equal', () => { + let group = new FormGroup({f1: new FormControl('a'), f2: new FormControl('b')}); + let validator = Validators.equalsTo('f2'); + expect(validator(group.controls['f1'])).toEqual({equalsTo: {unequalField: 'f2'}}); + }); + + it('should throw if passed a form control', () => { + let validator = Validators.equalsTo('f1', 'f2'); + // cast it to any so we don't get TS errors + expect(() => validator(new FormGroup({f1: new FormControl('')}))).toThrow(); + }); + + it('should throw if passed a form array', () => { + let validator = Validators.equalsTo('f1', 'f2'); + // cast it to any so we don't get TS errors + expect(() => validator(new FormArray([]))).toThrow(); + }); + + it('should throw if not passed any field to compare', () => { + let validator = Validators.equalsTo(); + expect(() => validator(new FormControl('a'))).toThrow(); + }); + + it('should throw if field passed does not exist in the group', () => { + let group = new FormGroup({f1: new FormControl('a'), f2: new FormControl('b')}); + let validator = Validators.equalsTo('f3', 'f4'); + // cast it to any so we don't get TS errors + expect(() => validator(new FormControl('a'))).toThrow(); + }); + }); + describe('required', () => { it('should error on an empty string', () => { expect(Validators.required(new FormControl(''))).toEqual({'required': true}); }); diff --git a/tools/public_api_guard/forms/index.d.ts b/tools/public_api_guard/forms/index.d.ts index 5d0f1166e3..ad38179806 100644 --- a/tools/public_api_guard/forms/index.d.ts +++ b/tools/public_api_guard/forms/index.d.ts @@ -543,6 +543,7 @@ export declare class Validators { static email(control: AbstractControl): { [key: string]: boolean; }; + static equalsTo(...fieldPaths: string[]): ValidatorFn; static maxLength(maxLength: number): ValidatorFn; static minLength(minLength: number): ValidatorFn; static nullValidator(c: AbstractControl): {