diff --git a/packages/forms/src/directives/abstract_control_directive.ts b/packages/forms/src/directives/abstract_control_directive.ts index 514d23d196..f2cef2e96f 100644 --- a/packages/forms/src/directives/abstract_control_directive.ts +++ b/packages/forms/src/directives/abstract_control_directive.ts @@ -8,6 +8,7 @@ import {Observable} from 'rxjs/Observable'; import {AbstractControl} from '../model'; +import {ValidationErrors} from './validators'; /** * Base class for control directives. @@ -27,7 +28,7 @@ export abstract class AbstractControlDirective { get pending(): boolean { return this.control ? this.control.pending : null; } - get errors(): {[key: string]: any} { return this.control ? this.control.errors : null; } + get errors(): ValidationErrors|null { return this.control ? this.control.errors : null; } get pristine(): boolean { return this.control ? this.control.pristine : null; } diff --git a/packages/forms/src/directives/validators.ts b/packages/forms/src/directives/validators.ts index 7813e08eac..92671bb147 100644 --- a/packages/forms/src/directives/validators.ts +++ b/packages/forms/src/directives/validators.ts @@ -11,6 +11,11 @@ import {Observable} from 'rxjs/Observable'; import {AbstractControl} from '../model'; import {NG_VALIDATORS, Validators} from '../validators'; +/** @experimental */ +export type ValidationErrors = { + [key: string]: any +}; + /** * An interface that can be implemented by classes that can act as validators. * @@ -31,13 +36,13 @@ import {NG_VALIDATORS, Validators} from '../validators'; * @stable */ export interface Validator { - validate(c: AbstractControl): {[key: string]: any}; + validate(c: AbstractControl): ValidationErrors|null; registerOnValidatorChange?(fn: () => void): void; } /** @experimental */ export interface AsyncValidator extends Validator { - validate(c: AbstractControl): Promise<{[key: string]: any}>|Observable<{[key: string]: any}>; + validate(c: AbstractControl): Promise|Observable; } export const REQUIRED_VALIDATOR: Provider = { @@ -75,14 +80,14 @@ export class RequiredValidator implements Validator { private _onChange: () => void; @Input() - get required(): boolean /*| string*/ { return this._required; } + get required(): boolean|string { return this._required; } - set required(value: boolean) { + set required(value: boolean|string) { this._required = value != null && value !== false && `${value}` !== 'false'; if (this._onChange) this._onChange(); } - validate(c: AbstractControl): {[key: string]: any} { + validate(c: AbstractControl): ValidationErrors|null { return this.required ? Validators.required(c) : null; } @@ -108,7 +113,7 @@ export class RequiredValidator implements Validator { host: {'[attr.required]': 'required ? "" : null'} }) export class CheckboxRequiredValidator extends RequiredValidator { - validate(c: AbstractControl): {[key: string]: any} { + validate(c: AbstractControl): ValidationErrors|null { return this.required ? Validators.requiredTrue(c) : null; } } @@ -150,7 +155,7 @@ export class EmailValidator implements Validator { if (this._onChange) this._onChange(); } - validate(c: AbstractControl): {[key: string]: any} { + validate(c: AbstractControl): ValidationErrors|null { return this._enabled ? Validators.email(c) : null; } @@ -160,13 +165,13 @@ export class EmailValidator implements Validator { /** * @stable */ -export interface ValidatorFn { (c: AbstractControl): {[key: string]: any}; } +export interface ValidatorFn { (c: AbstractControl): ValidationErrors|null; } /** * @stable */ export interface AsyncValidatorFn { - (c: AbstractControl): Promise<{[key: string]: any}>|Observable<{[key: string]: any}>; + (c: AbstractControl): Promise|Observable; } /** @@ -207,7 +212,7 @@ export class MinLengthValidator implements Validator, } } - validate(c: AbstractControl): {[key: string]: any} { + validate(c: AbstractControl): ValidationErrors|null { return this.minlength == null ? null : this._validator(c); } @@ -257,7 +262,7 @@ export class MaxLengthValidator implements Validator, } } - validate(c: AbstractControl): {[key: string]: any} { + validate(c: AbstractControl): ValidationErrors|null { return this.maxlength != null ? this._validator(c) : null; } @@ -299,7 +304,7 @@ export class PatternValidator implements Validator, private _validator: ValidatorFn; private _onChange: () => void; - @Input() pattern: string /*|RegExp*/; + @Input() pattern: string|RegExp; ngOnChanges(changes: SimpleChanges): void { if ('pattern' in changes) { @@ -308,7 +313,7 @@ export class PatternValidator implements Validator, } } - validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); } + validate(c: AbstractControl): ValidationErrors|null { return this._validator(c); } registerOnValidatorChange(fn: () => void): void { this._onChange = fn; } diff --git a/packages/forms/src/forms.ts b/packages/forms/src/forms.ts index d521311688..f0ec1cbd00 100644 --- a/packages/forms/src/forms.ts +++ b/packages/forms/src/forms.ts @@ -38,9 +38,10 @@ export {FormArrayName} from './directives/reactive_directives/form_group_name'; export {FormGroupName} from './directives/reactive_directives/form_group_name'; export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor'; export {SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor'; -export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator, ValidatorFn} from './directives/validators'; +export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, ValidationErrors, Validator, ValidatorFn} from './directives/validators'; export {FormBuilder} from './form_builder'; export {AbstractControl, FormArray, FormControl, FormGroup} from './model'; export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators'; export {VERSION} from './version'; + export * from './form_providers'; diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index 470bc16997..cd00942f60 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -8,13 +8,10 @@ import {EventEmitter} from '@angular/core'; import {Observable} from 'rxjs/Observable'; - import {composeAsyncValidators, composeValidators} from './directives/shared'; -import {AsyncValidatorFn, ValidatorFn} from './directives/validators'; +import {AsyncValidatorFn, ValidationErrors, ValidatorFn} from './directives/validators'; import {toObservable} from './validators'; - - /** * Indicates that a FormControl is valid, i.e. that no errors exist in the input value. */ @@ -57,6 +54,7 @@ function _find(control: AbstractControl, path: Array| string, del return null; }, control); } + function coerceToValidator(validator: ValidatorFn | ValidatorFn[]): ValidatorFn { return Array.isArray(validator) ? composeValidators(validator) : validator; } @@ -86,7 +84,7 @@ export abstract class AbstractControl { private _valueChanges: EventEmitter; private _statusChanges: EventEmitter; private _status: string; - private _errors: {[key: string]: any}; + private _errors: ValidationErrors|null; private _pristine: boolean = true; private _touched: boolean = false; private _parent: FormGroup|FormArray; @@ -163,7 +161,7 @@ export abstract class AbstractControl { * Returns any errors generated by failing validation. If there * are no errors, it will return null. */ - get errors(): {[key: string]: any} { return this._errors; } + get errors(): ValidationErrors|null { return this._errors; } /** * A control is `pristine` if the user has not yet changed @@ -407,7 +405,7 @@ export abstract class AbstractControl { private _setInitialStatus() { this._status = this._allControlsDisabled() ? DISABLED : VALID; } - private _runValidator(): {[key: string]: any} { + private _runValidator(): ValidationErrors|null { return this.validator ? this.validator(this) : null; } @@ -416,7 +414,7 @@ export abstract class AbstractControl { this._status = PENDING; const obs = toObservable(this.asyncValidator(this)); this._asyncValidationSubscription = - obs.subscribe((res: {[key: string]: any}) => this.setErrors(res, {emitEvent})); + obs.subscribe((errors: ValidationErrors | null) => this.setErrors(errors, {emitEvent})); } } @@ -449,7 +447,7 @@ export abstract class AbstractControl { * expect(login.valid).toEqual(true); * ``` */ - setErrors(errors: {[key: string]: any}, {emitEvent}: {emitEvent?: boolean} = {}): void { + setErrors(errors: ValidationErrors|null, {emitEvent}: {emitEvent?: boolean} = {}): void { this._errors = errors; this._updateControlsErrors(emitEvent !== false); } diff --git a/packages/forms/src/validators.ts b/packages/forms/src/validators.ts index c0c5c235d5..d693a9252f 100644 --- a/packages/forms/src/validators.ts +++ b/packages/forms/src/validators.ts @@ -11,9 +11,8 @@ import {Observable} from 'rxjs/Observable'; import {forkJoin} from 'rxjs/observable/forkJoin'; import {fromPromise} from 'rxjs/observable/fromPromise'; import {map} from 'rxjs/operator/map'; - -import {AsyncValidatorFn, Validator, ValidatorFn} from './directives/validators'; -import {AbstractControl, FormControl, FormGroup} from './model'; +import {AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators'; +import {AbstractControl, FormControl} from './model'; function isEmptyInputValue(value: any): boolean { // we don't check for string here so it also works with arrays @@ -66,21 +65,21 @@ export class Validators { /** * Validator that requires controls to have a non-empty value. */ - static required(control: AbstractControl): {[key: string]: boolean} { + static required(control: AbstractControl): ValidationErrors|null { return isEmptyInputValue(control.value) ? {'required': true} : null; } /** * Validator that requires control value to be true. */ - static requiredTrue(control: AbstractControl): {[key: string]: boolean} { + static requiredTrue(control: AbstractControl): ValidationErrors|null { return control.value === true ? null : {'required': true}; } /** * Validator that performs email validation. */ - static email(control: AbstractControl): {[key: string]: boolean} { + static email(control: AbstractControl): ValidationErrors|null { return EMAIL_REGEXP.test(control.value) ? null : {'email': true}; } @@ -88,7 +87,7 @@ export class Validators { * Validator that requires controls to have a value of a minimum length. */ static minLength(minLength: number): ValidatorFn { - return (control: AbstractControl): {[key: string]: any} => { + return (control: AbstractControl): ValidationErrors | null => { if (isEmptyInputValue(control.value)) { return null; // don't validate empty values to allow optional controls } @@ -103,7 +102,7 @@ export class Validators { * Validator that requires controls to have a value of a maximum length. */ static maxLength(maxLength: number): ValidatorFn { - return (control: AbstractControl): {[key: string]: any} => { + return (control: AbstractControl): ValidationErrors | null => { const length: number = control.value ? control.value.length : 0; return length > maxLength ? {'maxlength': {'requiredLength': maxLength, 'actualLength': length}} : @@ -125,7 +124,7 @@ export class Validators { regexStr = pattern.toString(); regex = pattern; } - return (control: AbstractControl): {[key: string]: any} => { + return (control: AbstractControl): ValidationErrors | null => { if (isEmptyInputValue(control.value)) { return null; // don't validate empty values to allow optional controls } @@ -138,7 +137,7 @@ export class Validators { /** * No-op validator. */ - static nullValidator(c: AbstractControl): {[key: string]: boolean} { return null; } + static nullValidator(c: AbstractControl): ValidationErrors|null { return null; } /** * Compose multiple validators into a single function that returns the union @@ -186,9 +185,9 @@ function _executeAsyncValidators(control: AbstractControl, validators: AsyncVali return validators.map(v => v(control)); } -function _mergeErrors(arrayOfErrors: any[]): {[key: string]: any} { +function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null { const res: {[key: string]: any} = - arrayOfErrors.reduce((res: {[key: string]: any}, errors: {[key: string]: any}) => { + arrayOfErrors.reduce((res: ValidationErrors | null, errors: ValidationErrors | null) => { return errors != null ? merge(res, errors) : res; }, {}); return Object.keys(res).length === 0 ? null : res; diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index 15b1ed7c65..d5335f8c88 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -6,29 +6,28 @@ * found in the LICENSE file at https://angular.io/license */ -import {SimpleChange} from '@angular/core/src/change_detection'; +import {SimpleChange} from '@angular/core'; import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal'; -import {CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, Validator, Validators} from '@angular/forms'; -import {composeValidators, selectValueAccessor} from '@angular/forms/src/directives/shared'; - +import {AbstractControl, CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, ValidationErrors, Validator, Validators} from '@angular/forms'; +import {composeValidators, selectValueAccessor} from '../src/directives/shared'; import {SpyNgControl, SpyValueAccessor} from './spies'; class DummyControlValueAccessor implements ControlValueAccessor { writtenValue: any; - registerOnChange(fn: any /** TODO #9100 */) {} - registerOnTouched(fn: any /** TODO #9100 */) {} + registerOnChange(fn: any) {} + registerOnTouched(fn: any) {} writeValue(obj: any): void { this.writtenValue = obj; } } class CustomValidatorDirective implements Validator { - validate(c: FormControl): {[key: string]: any} { return {'custom': true}; } + validate(c: FormControl): ValidationErrors { return {'custom': true}; } } -function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) { - return (c: any /** TODO #9100 */) => { +function asyncValidator(expected: any, timeout = 0) { + return (c: AbstractControl): any => { let resolve: (result: any) => void; const promise = new Promise(res => { resolve = res; }); const res = c.value != expected ? {'async': true} : null; @@ -228,7 +227,7 @@ export function main() { }); describe('addFormGroup', () => { - const matchingPasswordsValidator = (g: any /** TODO #9100 */) => { + const matchingPasswordsValidator = (g: FormGroup) => { if (g.controls['password'].value != g.controls['passwordConfirm'].value) { return {'differentPasswords': true}; } else { @@ -283,7 +282,7 @@ export function main() { }); it('should set up a sync validator', () => { - const formValidator = (c: any /** TODO #9100 */) => ({'custom': true}); + const formValidator = (c: AbstractControl) => ({'custom': true}); const f = new FormGroupDirective([formValidator], []); f.form = formModel; f.ngOnChanges({'form': new SimpleChange(null, null, false)}); @@ -475,7 +474,7 @@ export function main() { describe('FormControlDirective', () => { let controlDir: any /** TODO #9100 */; let control: any /** TODO #9100 */; - const checkProperties = function(control: any /** TODO #9100 */) { + const checkProperties = function(control: AbstractControl) { expect(controlDir.control).toBe(control); expect(controlDir.value).toBe(control.value); expect(controlDir.valid).toBe(control.valid); diff --git a/packages/forms/test/validators_spec.ts b/packages/forms/test/validators_spec.ts index 08c57e8185..da1f6824dd 100644 --- a/packages/forms/test/validators_spec.ts +++ b/packages/forms/test/validators_spec.ts @@ -8,20 +8,19 @@ import {fakeAsync, tick} from '@angular/core/testing'; import {describe, expect, it} from '@angular/core/testing/src/testing_internal'; -import {AbstractControl, AsyncValidatorFn, FormArray, FormControl, FormGroup, Validators} from '@angular/forms'; +import {AbstractControl, AsyncValidatorFn, FormArray, FormControl, Validators} from '@angular/forms'; import {Observable} from 'rxjs/Observable'; import {of } from 'rxjs/observable/of'; import {timer} from 'rxjs/observable/timer'; import {first} from 'rxjs/operator/first'; import {map} from 'rxjs/operator/map'; - import {normalizeAsyncValidator} from '../src/directives/normalize_validator'; -import {AsyncValidator} from '../src/directives/validators'; +import {AsyncValidator, ValidationErrors, ValidatorFn} from '../src/directives/validators'; export function main() { - function validator(key: string, error: any) { - return function(c: AbstractControl) { - const r: {[k: string]: string} = {}; + function validator(key: string, error: any): ValidatorFn { + return (c: AbstractControl) => { + const r: ValidationErrors = {}; r[key] = error; return r; }; @@ -30,8 +29,7 @@ export function main() { class AsyncValidatorDirective implements AsyncValidator { constructor(private expected: string, private error: any) {} - validate(c: any): Observable < { [key: string]: any; } - > { + validate(c: any): Observable { return Observable.create((obs: any) => { const error = this.expected !== c.value ? this.error : null; obs.next(error); diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts index bee02b8f1c..888e2d4566 100644 --- a/tools/public_api_guard/forms/forms.d.ts +++ b/tools/public_api_guard/forms/forms.d.ts @@ -4,9 +4,7 @@ export declare abstract class AbstractControl { readonly dirty: boolean; readonly disabled: boolean; readonly enabled: boolean; - readonly errors: { - [key: string]: any; - }; + readonly errors: ValidationErrors | null; readonly invalid: boolean; readonly parent: FormGroup | FormArray; readonly pending: boolean; @@ -52,9 +50,7 @@ export declare abstract class AbstractControl { abstract patchValue(value: any, options?: Object): void; abstract reset(value?: any, options?: Object): void; setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void; - setErrors(errors: { - [key: string]: any; - }, {emitEvent}?: { + setErrors(errors: ValidationErrors | null, {emitEvent}?: { emitEvent?: boolean; }): void; setParent(parent: FormGroup | FormArray): void; @@ -72,9 +68,7 @@ export declare abstract class AbstractControlDirective { readonly dirty: boolean; readonly disabled: boolean; readonly enabled: boolean; - readonly errors: { - [key: string]: any; - }; + readonly errors: ValidationErrors | null; readonly invalid: boolean; readonly path: string[]; readonly pending: boolean; @@ -103,20 +97,12 @@ export declare class AbstractFormGroupDirective extends ControlContainer impleme /** @experimental */ export interface AsyncValidator extends Validator { - validate(c: AbstractControl): Promise<{ - [key: string]: any; - }> | Observable<{ - [key: string]: any; - }>; + validate(c: AbstractControl): Promise | Observable; } /** @stable */ export interface AsyncValidatorFn { - (c: AbstractControl): Promise<{ - [key: string]: any; - }> | Observable<{ - [key: string]: any; - }>; + (c: AbstractControl): Promise | Observable; } /** @stable */ @@ -132,9 +118,7 @@ export declare class CheckboxControlValueAccessor implements ControlValueAccesso /** @experimental */ export declare class CheckboxRequiredValidator extends RequiredValidator { - validate(c: AbstractControl): { - [key: string]: any; - }; + validate(c: AbstractControl): ValidationErrors | null; } /** @stable */ @@ -167,9 +151,7 @@ export declare class DefaultValueAccessor implements ControlValueAccessor { export declare class EmailValidator implements Validator { email: boolean | string; registerOnValidatorChange(fn: () => void): void; - validate(c: AbstractControl): { - [key: string]: any; - }; + validate(c: AbstractControl): ValidationErrors | null; } /** @stable */ @@ -361,9 +343,7 @@ export declare class MaxLengthValidator implements Validator, OnChanges { maxlength: string; ngOnChanges(changes: SimpleChanges): void; registerOnValidatorChange(fn: () => void): void; - validate(c: AbstractControl): { - [key: string]: any; - }; + validate(c: AbstractControl): ValidationErrors | null; } /** @stable */ @@ -371,9 +351,7 @@ export declare class MinLengthValidator implements Validator, OnChanges { minlength: string; ngOnChanges(changes: SimpleChanges): void; registerOnValidatorChange(fn: () => void): void; - validate(c: AbstractControl): { - [key: string]: any; - }; + validate(c: AbstractControl): ValidationErrors | null; } /** @stable */ @@ -472,12 +450,10 @@ export declare class NgSelectOption implements OnDestroy { /** @stable */ export declare class PatternValidator implements Validator, OnChanges { - pattern: string; + pattern: string | RegExp; ngOnChanges(changes: SimpleChanges): void; registerOnValidatorChange(fn: () => void): void; - validate(c: AbstractControl): { - [key: string]: any; - }; + validate(c: AbstractControl): ValidationErrors | null; } /** @stable */ @@ -503,11 +479,9 @@ export declare class ReactiveFormsModule { /** @stable */ export declare class RequiredValidator implements Validator { - required: boolean; + required: boolean | string; registerOnValidatorChange(fn: () => void): void; - validate(c: AbstractControl): { - [key: string]: any; - }; + validate(c: AbstractControl): ValidationErrors | null; } /** @stable */ @@ -536,40 +510,33 @@ export declare class SelectMultipleControlValueAccessor implements ControlValueA writeValue(value: any): void; } +/** @experimental */ +export declare type ValidationErrors = { + [key: string]: any; +}; + /** @stable */ export interface Validator { registerOnValidatorChange?(fn: () => void): void; - validate(c: AbstractControl): { - [key: string]: any; - }; + validate(c: AbstractControl): ValidationErrors | null; } /** @stable */ export interface ValidatorFn { - (c: AbstractControl): { - [key: string]: any; - }; + (c: AbstractControl): ValidationErrors | null; } /** @stable */ export declare class Validators { static compose(validators: ValidatorFn[]): ValidatorFn; static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn; - static email(control: AbstractControl): { - [key: string]: boolean; - }; + static email(control: AbstractControl): ValidationErrors | null; static maxLength(maxLength: number): ValidatorFn; static minLength(minLength: number): ValidatorFn; - static nullValidator(c: AbstractControl): { - [key: string]: boolean; - }; + static nullValidator(c: AbstractControl): ValidationErrors | null; static pattern(pattern: string | RegExp): ValidatorFn; - static required(control: AbstractControl): { - [key: string]: boolean; - }; - static requiredTrue(control: AbstractControl): { - [key: string]: boolean; - }; + static required(control: AbstractControl): ValidationErrors | null; + static requiredTrue(control: AbstractControl): ValidationErrors | null; } /** @stable */