From 551fe50ebd8d15237bfeb13892e9f39eca11c72f Mon Sep 17 00:00:00 2001 From: Dzmitry Shylovich Date: Tue, 21 Feb 2017 03:26:51 +0300 Subject: [PATCH] feat(forms): introduce AsyncValidator interface (#13483) Closes #13398 --- .../@angular/forms/src/directives/ng_control.ts | 4 ++-- modules/@angular/forms/src/directives/ng_model.ts | 4 ++-- .../forms/src/directives/normalize_validator.ts | 10 +++++----- .../reactive_directives/form_control_directive.ts | 4 ++-- .../reactive_directives/form_control_name.ts | 4 ++-- modules/@angular/forms/src/directives/shared.ts | 4 ++-- .../@angular/forms/src/directives/validators.ts | 7 +++++++ modules/@angular/forms/src/forms.ts | 2 +- .../forms/test/reactive_integration_spec.ts | 4 ++-- .../forms/test/template_integration_spec.ts | 4 ++-- modules/@angular/forms/test/validators_spec.ts | 6 ++++-- tools/public_api_guard/forms/index.d.ts | 15 ++++++++++++--- 12 files changed, 43 insertions(+), 25 deletions(-) diff --git a/modules/@angular/forms/src/directives/ng_control.ts b/modules/@angular/forms/src/directives/ng_control.ts index 55cc00a342..7f2d4b04cc 100644 --- a/modules/@angular/forms/src/directives/ng_control.ts +++ b/modules/@angular/forms/src/directives/ng_control.ts @@ -10,7 +10,7 @@ import {AbstractControlDirective} from './abstract_control_directive'; import {ControlContainer} from './control_container'; import {ControlValueAccessor} from './control_value_accessor'; -import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; +import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; function unimplemented(): any { throw new Error('unimplemented'); @@ -32,7 +32,7 @@ export abstract class NgControl extends AbstractControlDirective { /** @internal */ _rawValidators: Array = []; /** @internal */ - _rawAsyncValidators: Array = []; + _rawAsyncValidators: Array = []; get validator(): ValidatorFn { return unimplemented(); } get asyncValidator(): AsyncValidatorFn { return unimplemented(); } diff --git a/modules/@angular/forms/src/directives/ng_model.ts b/modules/@angular/forms/src/directives/ng_model.ts index 2c0debe45b..750fa3663e 100644 --- a/modules/@angular/forms/src/directives/ng_model.ts +++ b/modules/@angular/forms/src/directives/ng_model.ts @@ -20,7 +20,7 @@ import {NgForm} from './ng_form'; import {NgModelGroup} from './ng_model_group'; import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared'; import {TemplateDrivenErrors} from './template_driven_errors'; -import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; +import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; export const formControlBinding: any = { provide: NgControl, @@ -136,7 +136,7 @@ export class NgModel extends NgControl implements OnChanges, constructor(@Optional() @Host() parent: ControlContainer, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array, - @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, + @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); diff --git a/modules/@angular/forms/src/directives/normalize_validator.ts b/modules/@angular/forms/src/directives/normalize_validator.ts index 9d2328076b..3a08e61558 100644 --- a/modules/@angular/forms/src/directives/normalize_validator.ts +++ b/modules/@angular/forms/src/directives/normalize_validator.ts @@ -7,8 +7,7 @@ */ import {AbstractControl} from '../model'; - -import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; +import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn { if ((validator).validate) { @@ -18,9 +17,10 @@ export function normalizeValidator(validator: ValidatorFn | Validator): Validato } } -export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn { - if ((validator).validate) { - return (c: AbstractControl) => (validator).validate(c); +export function normalizeAsyncValidator(validator: AsyncValidatorFn | AsyncValidator): + AsyncValidatorFn { + if ((validator).validate) { + return (c: AbstractControl) => (validator).validate(c); } else { return validator; } diff --git a/modules/@angular/forms/src/directives/reactive_directives/form_control_directive.ts b/modules/@angular/forms/src/directives/reactive_directives/form_control_directive.ts index 4db8110432..0fda64f0ff 100644 --- a/modules/@angular/forms/src/directives/reactive_directives/form_control_directive.ts +++ b/modules/@angular/forms/src/directives/reactive_directives/form_control_directive.ts @@ -15,7 +15,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor import {NgControl} from '../ng_control'; import {ReactiveErrors} from '../reactive_errors'; import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared'; -import {AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; +import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; export const formControlBinding: any = { provide: NgControl, @@ -77,7 +77,7 @@ export class FormControlDirective extends NgControl implements OnChanges { set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); } constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array, - @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, + @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); diff --git a/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts b/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts index 2b4a29224b..ca91ed5419 100644 --- a/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts +++ b/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts @@ -17,7 +17,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor import {NgControl} from '../ng_control'; import {ReactiveErrors} from '../reactive_errors'; import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared'; -import {AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; +import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; import {FormGroupDirective} from './form_group_directive'; import {FormArrayName, FormGroupName} from './form_group_name'; @@ -98,7 +98,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { @Optional() @Host() @SkipSelf() parent: ControlContainer, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array, @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: - Array, + Array, @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); this._parent = parent; diff --git a/modules/@angular/forms/src/directives/shared.ts b/modules/@angular/forms/src/directives/shared.ts index 4b3a8c4eeb..6a0d397d98 100644 --- a/modules/@angular/forms/src/directives/shared.ts +++ b/modules/@angular/forms/src/directives/shared.ts @@ -24,7 +24,7 @@ import {RangeValueAccessor} from './range_value_accessor'; import {FormArrayName} from './reactive_directives/form_group_name'; import {SelectControlValueAccessor} from './select_control_value_accessor'; import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor'; -import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; +import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; export function controlPath(name: string, parent: ControlContainer): string[] { @@ -68,7 +68,7 @@ export function setUpControl(control: FormControl, dir: NgControl): void { (validator).registerOnValidatorChange(() => control.updateValueAndValidity()); }); - dir._rawAsyncValidators.forEach((validator: Validator | ValidatorFn) => { + dir._rawAsyncValidators.forEach((validator: AsyncValidator | AsyncValidatorFn) => { if ((validator).registerOnValidatorChange) (validator).registerOnValidatorChange(() => control.updateValueAndValidity()); }); diff --git a/modules/@angular/forms/src/directives/validators.ts b/modules/@angular/forms/src/directives/validators.ts index cc2805e1fc..ab82af4275 100644 --- a/modules/@angular/forms/src/directives/validators.ts +++ b/modules/@angular/forms/src/directives/validators.ts @@ -5,7 +5,9 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import {Directive, Input, OnChanges, Provider, SimpleChanges, forwardRef} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; import {AbstractControl} from '../model'; import {NG_VALIDATORS, Validators} from '../validators'; @@ -33,6 +35,11 @@ export interface Validator { registerOnValidatorChange?(fn: () => void): void; } +/** @experimental */ +export interface AsyncValidator extends Validator { + validate(c: AbstractControl): Promise<{[key: string]: any}>|Observable<{[key: string]: any}>; +} + export const REQUIRED_VALIDATOR: Provider = { provide: NG_VALIDATORS, useExisting: forwardRef(() => RequiredValidator), diff --git a/modules/@angular/forms/src/forms.ts b/modules/@angular/forms/src/forms.ts index 9bfea3aea1..d521311688 100644 --- a/modules/@angular/forms/src/forms.ts +++ b/modules/@angular/forms/src/forms.ts @@ -38,7 +38,7 @@ 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 {AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator, ValidatorFn} from './directives/validators'; +export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, 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'; diff --git a/modules/@angular/forms/test/reactive_integration_spec.ts b/modules/@angular/forms/test/reactive_integration_spec.ts index 9e2e878b19..39dd20b24e 100644 --- a/modules/@angular/forms/test/reactive_integration_spec.ts +++ b/modules/@angular/forms/test/reactive_integration_spec.ts @@ -8,7 +8,7 @@ import {Component, Directive, EventEmitter, Input, Output, Type, forwardRef} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; -import {AbstractControl, ControlValueAccessor, FormArray, FormControl, FormGroup, FormGroupDirective, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule, Validator, Validators} from '@angular/forms'; +import {AbstractControl, AsyncValidator, ControlValueAccessor, FormArray, FormControl, FormGroup, FormGroupDirective, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule, Validators} from '@angular/forms'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {dispatchEvent} from '@angular/platform-browser/testing/browser_util'; @@ -1951,7 +1951,7 @@ class LoginIsEmptyValidator { multi: true }] }) -class UniqLoginValidator implements Validator { +class UniqLoginValidator implements AsyncValidator { @Input('uniq-login-validator') expected: any; validate(c: AbstractControl) { return uniqLoginAsyncValidator(this.expected)(c); } diff --git a/modules/@angular/forms/test/template_integration_spec.ts b/modules/@angular/forms/test/template_integration_spec.ts index ff4af17df1..f7850644e1 100644 --- a/modules/@angular/forms/test/template_integration_spec.ts +++ b/modules/@angular/forms/test/template_integration_spec.ts @@ -8,7 +8,7 @@ import {Component, Directive, Input, Type, forwardRef} from '@angular/core'; import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing'; -import {AbstractControl, ControlValueAccessor, FormsModule, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, NgForm, Validator} from '@angular/forms'; +import {AbstractControl, AsyncValidator, ControlValueAccessor, FormsModule, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, NgForm} from '@angular/forms'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {dispatchEvent} from '@angular/platform-browser/testing/browser_util'; @@ -1493,7 +1493,7 @@ class NgModelEmailValidator { {provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => NgAsyncValidator), multi: true} ] }) -class NgAsyncValidator implements Validator { +class NgAsyncValidator implements AsyncValidator { validate(c: AbstractControl) { return Promise.resolve(null); } } diff --git a/modules/@angular/forms/test/validators_spec.ts b/modules/@angular/forms/test/validators_spec.ts index 0b9d5574e8..54b31285ff 100644 --- a/modules/@angular/forms/test/validators_spec.ts +++ b/modules/@angular/forms/test/validators_spec.ts @@ -12,6 +12,7 @@ import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@a import {Observable} from 'rxjs/Observable'; import {normalizeAsyncValidator} from '../src/directives/normalize_validator'; +import {AsyncValidator} from '../src/directives/validators'; import {EventEmitter} from '../src/facade/async'; export function main() { @@ -23,10 +24,11 @@ export function main() { }; } - class AsyncValidatorDirective { + class AsyncValidatorDirective implements AsyncValidator { constructor(private expected: string, private error: any) {} - validate(c: any): {[key: string]: any;} { + validate(c: any): Observable < { [key: string]: any; } + > { 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/index.d.ts b/tools/public_api_guard/forms/index.d.ts index 447f7e002f..79f72f8f62 100644 --- a/tools/public_api_guard/forms/index.d.ts +++ b/tools/public_api_guard/forms/index.d.ts @@ -101,6 +101,15 @@ export declare class AbstractFormGroupDirective extends ControlContainer impleme ngOnInit(): void; } +/** @experimental */ +export interface AsyncValidator extends Validator { + validate(c: AbstractControl): Promise<{ + [key: string]: any; + }> | Observable<{ + [key: string]: any; + }>; +} + /** @stable */ export interface AsyncValidatorFn { (c: AbstractControl): any; @@ -253,7 +262,7 @@ export declare class FormControlDirective extends NgControl implements OnChanges update: EventEmitter<{}>; readonly validator: ValidatorFn; viewModel: any; - constructor(validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); + constructor(validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; viewToModelUpdate(newValue: any): void; } @@ -269,7 +278,7 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD readonly path: string[]; update: EventEmitter<{}>; readonly validator: ValidatorFn; - constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); + constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; viewToModelUpdate(newValue: any): void; @@ -434,7 +443,7 @@ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { update: EventEmitter<{}>; readonly validator: ValidatorFn; viewModel: any; - constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); + constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); compositionEnd(): void; compositionStart(): void; ngOnChanges(changes: SimpleChanges): void;