feat(forms): introduce AsyncValidator interface (#13483)

Closes #13398
This commit is contained in:
Dzmitry Shylovich 2017-02-21 03:26:51 +03:00 committed by Victor Berchet
parent d6a58f9f70
commit 551fe50ebd
12 changed files with 43 additions and 25 deletions

View File

@ -10,7 +10,7 @@
import {AbstractControlDirective} from './abstract_control_directive'; import {AbstractControlDirective} from './abstract_control_directive';
import {ControlContainer} from './control_container'; import {ControlContainer} from './control_container';
import {ControlValueAccessor} from './control_value_accessor'; import {ControlValueAccessor} from './control_value_accessor';
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
function unimplemented(): any { function unimplemented(): any {
throw new Error('unimplemented'); throw new Error('unimplemented');
@ -32,7 +32,7 @@ export abstract class NgControl extends AbstractControlDirective {
/** @internal */ /** @internal */
_rawValidators: Array<Validator|ValidatorFn> = []; _rawValidators: Array<Validator|ValidatorFn> = [];
/** @internal */ /** @internal */
_rawAsyncValidators: Array<Validator|ValidatorFn> = []; _rawAsyncValidators: Array<AsyncValidator|AsyncValidatorFn> = [];
get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); } get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); } get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }

View File

@ -20,7 +20,7 @@ import {NgForm} from './ng_form';
import {NgModelGroup} from './ng_model_group'; import {NgModelGroup} from './ng_model_group';
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared'; import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
import {TemplateDrivenErrors} from './template_driven_errors'; import {TemplateDrivenErrors} from './template_driven_errors';
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
export const formControlBinding: any = { export const formControlBinding: any = {
provide: NgControl, provide: NgControl,
@ -136,7 +136,7 @@ export class NgModel extends NgControl implements OnChanges,
constructor(@Optional() @Host() parent: ControlContainer, constructor(@Optional() @Host() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<Validator|AsyncValidatorFn>, @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) @Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[]) { valueAccessors: ControlValueAccessor[]) {
super(); super();

View File

@ -7,8 +7,7 @@
*/ */
import {AbstractControl} from '../model'; import {AbstractControl} from '../model';
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn { export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
if ((<Validator>validator).validate) { if ((<Validator>validator).validate) {
@ -18,9 +17,10 @@ export function normalizeValidator(validator: ValidatorFn | Validator): Validato
} }
} }
export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn { export function normalizeAsyncValidator(validator: AsyncValidatorFn | AsyncValidator):
if ((<Validator>validator).validate) { AsyncValidatorFn {
return (c: AbstractControl) => (<Validator>validator).validate(c); if ((<AsyncValidator>validator).validate) {
return (c: AbstractControl) => (<AsyncValidator>validator).validate(c);
} else { } else {
return <AsyncValidatorFn>validator; return <AsyncValidatorFn>validator;
} }

View File

@ -15,7 +15,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor
import {NgControl} from '../ng_control'; import {NgControl} from '../ng_control';
import {ReactiveErrors} from '../reactive_errors'; import {ReactiveErrors} from '../reactive_errors';
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared'; 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 = { export const formControlBinding: any = {
provide: NgControl, provide: NgControl,
@ -77,7 +77,7 @@ export class FormControlDirective extends NgControl implements OnChanges {
set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); } set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>, constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<Validator|AsyncValidatorFn>, @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) @Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[]) { valueAccessors: ControlValueAccessor[]) {
super(); super();

View File

@ -17,7 +17,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor
import {NgControl} from '../ng_control'; import {NgControl} from '../ng_control';
import {ReactiveErrors} from '../reactive_errors'; import {ReactiveErrors} from '../reactive_errors';
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared'; 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 {FormGroupDirective} from './form_group_directive';
import {FormArrayName, FormGroupName} from './form_group_name'; 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() @Host() @SkipSelf() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:
Array<Validator|AsyncValidatorFn>, Array<AsyncValidator|AsyncValidatorFn>,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
super(); super();
this._parent = parent; this._parent = parent;

View File

@ -24,7 +24,7 @@ import {RangeValueAccessor} from './range_value_accessor';
import {FormArrayName} from './reactive_directives/form_group_name'; import {FormArrayName} from './reactive_directives/form_group_name';
import {SelectControlValueAccessor} from './select_control_value_accessor'; import {SelectControlValueAccessor} from './select_control_value_accessor';
import {SelectMultipleControlValueAccessor} from './select_multiple_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[] { export function controlPath(name: string, parent: ControlContainer): string[] {
@ -68,7 +68,7 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
(<Validator>validator).registerOnValidatorChange(() => control.updateValueAndValidity()); (<Validator>validator).registerOnValidatorChange(() => control.updateValueAndValidity());
}); });
dir._rawAsyncValidators.forEach((validator: Validator | ValidatorFn) => { dir._rawAsyncValidators.forEach((validator: AsyncValidator | AsyncValidatorFn) => {
if ((<Validator>validator).registerOnValidatorChange) if ((<Validator>validator).registerOnValidatorChange)
(<Validator>validator).registerOnValidatorChange(() => control.updateValueAndValidity()); (<Validator>validator).registerOnValidatorChange(() => control.updateValueAndValidity());
}); });

View File

@ -5,7 +5,9 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, Input, OnChanges, Provider, SimpleChanges, forwardRef} from '@angular/core'; import {Directive, Input, OnChanges, Provider, SimpleChanges, forwardRef} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {AbstractControl} from '../model'; import {AbstractControl} from '../model';
import {NG_VALIDATORS, Validators} from '../validators'; import {NG_VALIDATORS, Validators} from '../validators';
@ -33,6 +35,11 @@ export interface Validator {
registerOnValidatorChange?(fn: () => void): void; 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 = { export const REQUIRED_VALIDATOR: Provider = {
provide: NG_VALIDATORS, provide: NG_VALIDATORS,
useExisting: forwardRef(() => RequiredValidator), useExisting: forwardRef(() => RequiredValidator),

View File

@ -38,7 +38,7 @@ export {FormArrayName} from './directives/reactive_directives/form_group_name';
export {FormGroupName} 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 {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
export {SelectMultipleControlValueAccessor} from './directives/select_multiple_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 {FormBuilder} from './form_builder';
export {AbstractControl, FormArray, FormControl, FormGroup} from './model'; export {AbstractControl, FormArray, FormControl, FormGroup} from './model';
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators'; export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators';

View File

@ -8,7 +8,7 @@
import {Component, Directive, EventEmitter, Input, Output, Type, forwardRef} from '@angular/core'; import {Component, Directive, EventEmitter, Input, Output, Type, forwardRef} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; 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 {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util'; import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
@ -1951,7 +1951,7 @@ class LoginIsEmptyValidator {
multi: true multi: true
}] }]
}) })
class UniqLoginValidator implements Validator { class UniqLoginValidator implements AsyncValidator {
@Input('uniq-login-validator') expected: any; @Input('uniq-login-validator') expected: any;
validate(c: AbstractControl) { return uniqLoginAsyncValidator(this.expected)(c); } validate(c: AbstractControl) { return uniqLoginAsyncValidator(this.expected)(c); }

View File

@ -8,7 +8,7 @@
import {Component, Directive, Input, Type, forwardRef} from '@angular/core'; import {Component, Directive, Input, Type, forwardRef} from '@angular/core';
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing'; 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 {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util'; import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
@ -1493,7 +1493,7 @@ class NgModelEmailValidator {
{provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => NgAsyncValidator), multi: true} {provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => NgAsyncValidator), multi: true}
] ]
}) })
class NgAsyncValidator implements Validator { class NgAsyncValidator implements AsyncValidator {
validate(c: AbstractControl) { return Promise.resolve(null); } validate(c: AbstractControl) { return Promise.resolve(null); }
} }

View File

@ -12,6 +12,7 @@ import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@a
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {normalizeAsyncValidator} from '../src/directives/normalize_validator'; import {normalizeAsyncValidator} from '../src/directives/normalize_validator';
import {AsyncValidator} from '../src/directives/validators';
import {EventEmitter} from '../src/facade/async'; import {EventEmitter} from '../src/facade/async';
export function main() { export function main() {
@ -23,10 +24,11 @@ export function main() {
}; };
} }
class AsyncValidatorDirective { class AsyncValidatorDirective implements AsyncValidator {
constructor(private expected: string, private error: any) {} 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) => { return Observable.create((obs: any) => {
const error = this.expected !== c.value ? this.error : null; const error = this.expected !== c.value ? this.error : null;
obs.next(error); obs.next(error);

View File

@ -101,6 +101,15 @@ export declare class AbstractFormGroupDirective extends ControlContainer impleme
ngOnInit(): void; ngOnInit(): void;
} }
/** @experimental */
export interface AsyncValidator extends Validator {
validate(c: AbstractControl): Promise<{
[key: string]: any;
}> | Observable<{
[key: string]: any;
}>;
}
/** @stable */ /** @stable */
export interface AsyncValidatorFn { export interface AsyncValidatorFn {
(c: AbstractControl): any; (c: AbstractControl): any;
@ -253,7 +262,7 @@ export declare class FormControlDirective extends NgControl implements OnChanges
update: EventEmitter<{}>; update: EventEmitter<{}>;
readonly validator: ValidatorFn; readonly validator: ValidatorFn;
viewModel: any; viewModel: any;
constructor(validators: Array<Validator | ValidatorFn>, asyncValidators: Array<Validator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]); constructor(validators: Array<Validator | ValidatorFn>, asyncValidators: Array<AsyncValidator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]);
ngOnChanges(changes: SimpleChanges): void; ngOnChanges(changes: SimpleChanges): void;
viewToModelUpdate(newValue: any): void; viewToModelUpdate(newValue: any): void;
} }
@ -269,7 +278,7 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD
readonly path: string[]; readonly path: string[];
update: EventEmitter<{}>; update: EventEmitter<{}>;
readonly validator: ValidatorFn; readonly validator: ValidatorFn;
constructor(parent: ControlContainer, validators: Array<Validator | ValidatorFn>, asyncValidators: Array<Validator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]); constructor(parent: ControlContainer, validators: Array<Validator | ValidatorFn>, asyncValidators: Array<AsyncValidator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]);
ngOnChanges(changes: SimpleChanges): void; ngOnChanges(changes: SimpleChanges): void;
ngOnDestroy(): void; ngOnDestroy(): void;
viewToModelUpdate(newValue: any): void; viewToModelUpdate(newValue: any): void;
@ -434,7 +443,7 @@ export declare class NgModel extends NgControl implements OnChanges, OnDestroy {
update: EventEmitter<{}>; update: EventEmitter<{}>;
readonly validator: ValidatorFn; readonly validator: ValidatorFn;
viewModel: any; viewModel: any;
constructor(parent: ControlContainer, validators: Array<Validator | ValidatorFn>, asyncValidators: Array<Validator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]); constructor(parent: ControlContainer, validators: Array<Validator | ValidatorFn>, asyncValidators: Array<AsyncValidator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]);
compositionEnd(): void; compositionEnd(): void;
compositionStart(): void; compositionStart(): void;
ngOnChanges(changes: SimpleChanges): void; ngOnChanges(changes: SimpleChanges): void;