feat(forms): add support for adding async validators via template

Example:

@Directive({
  selector: '[uniq-login-validator]',
  providers: [provide(NG_ASYNC_VALIDATORS, {useExisting: UniqLoginValidator, multi: true})]
})
class UniqLoginValidator implements Validator {
  validate(c) { return someFunctionReturningPromiseOrObservable(); }
}
This commit is contained in:
vsavkin
2015-11-02 10:00:42 -08:00
committed by Victor Savkin
parent cf449ddaa9
commit 31c12af81f
14 changed files with 249 additions and 101 deletions

View File

@ -33,7 +33,7 @@ export {
SelectControlValueAccessor
} from './forms/directives/select_control_value_accessor';
export {FORM_DIRECTIVES} from './forms/directives';
export {NG_VALIDATORS, Validators} from './forms/validators';
export {NG_VALIDATORS, NG_ASYNC_VALIDATORS, Validators} from './forms/validators';
export {
RequiredValidator,
MinLengthValidator,

View File

@ -13,6 +13,7 @@ export abstract class NgControl extends AbstractControlDirective {
valueAccessor: ControlValueAccessor = null;
get validator(): Function { return unimplemented(); }
get asyncValidator(): Function { return unimplemented(); }
abstract viewToModelUpdate(newValue: any): void;
}

View File

@ -5,10 +5,10 @@ import {ListWrapper} from 'angular2/src/core/facade/collection';
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
import {ControlContainer} from './control_container';
import {controlPath} from './shared';
import {controlPath, composeValidators, composeAsyncValidators} from './shared';
import {ControlGroup} from '../model';
import {Form} from './form_interface';
import {Validators, NG_VALIDATORS} from '../validators';
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
const controlGroupProvider =
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgControlGroup)}));
@ -72,13 +72,11 @@ export class NgControlGroup extends ControlContainer implements OnInit,
/** @internal */
_parent: ControlContainer;
private _validators: Function[];
constructor(@Host() @SkipSelf() parent: ControlContainer,
@Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
@Optional() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
super();
this._parent = parent;
this._validators = validators;
}
onInit(): void { this.formDirective.addControlGroup(this); }
@ -100,5 +98,7 @@ export class NgControlGroup extends ControlContainer implements OnInit,
*/
get formDirective(): Form { return this._parent.formDirective; }
get validator(): Function { return Validators.compose(this._validators); }
get validator(): Function { return composeValidators(this._validators); }
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
}

View File

@ -8,9 +8,15 @@ import {forwardRef, Host, SkipSelf, Provider, Inject, Optional} from 'angular2/s
import {ControlContainer} from './control_container';
import {NgControl} from './ng_control';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {controlPath, composeValidators, isPropertyUpdated, selectValueAccessor} from './shared';
import {
controlPath,
composeValidators,
composeAsyncValidators,
isPropertyUpdated,
selectValueAccessor
} from './shared';
import {Control} from '../model';
import {Validators, NG_VALIDATORS} from '../validators';
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
const controlNameBinding =
@ -81,21 +87,18 @@ const controlNameBinding =
export class NgControlName extends NgControl implements OnChanges,
OnDestroy {
/** @internal */
_parent: ControlContainer;
update = new EventEmitter();
model: any;
viewModel: any;
private _validator: Function;
/** @internal */
_added = false;
private _added = false;
constructor(@Host() @SkipSelf() parent: ControlContainer,
@Optional() @Inject(NG_VALIDATORS) validators:
constructor(@Host() @SkipSelf() private _parent: ControlContainer,
@Optional() @Inject(NG_VALIDATORS) private _validators:
/* Array<Validator|Function> */ any[],
@Optional() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
/* Array<Validator|Function> */ any[],
@Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
super();
this._parent = parent;
this._validator = composeValidators(validators);
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
@ -121,7 +124,9 @@ export class NgControlName extends NgControl implements OnChanges,
get formDirective(): any { return this._parent.formDirective; }
get validator(): Function { return this._validator; }
get validator(): Function { return composeValidators(this._validators); }
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
get control(): Control { return this.formDirective.getControl(this); }
}

View File

@ -13,8 +13,8 @@ import {Form} from './form_interface';
import {NgControlGroup} from './ng_control_group';
import {ControlContainer} from './control_container';
import {AbstractControl, ControlGroup, Control} from '../model';
import {setUpControl, setUpControlGroup} from './shared';
import {Validators, NG_VALIDATORS} from '../validators';
import {setUpControl, setUpControlGroup, composeValidators, composeAsyncValidators} from './shared';
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
const formDirectiveProvider =
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgForm)}));
@ -91,9 +91,11 @@ export class NgForm extends ControlContainer implements Form {
form: ControlGroup;
ngSubmit = new EventEmitter();
constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
constructor(@Optional() @Inject(NG_VALIDATORS) validators: any[],
@Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
super();
this.form = new ControlGroup({}, null, Validators.compose(validators));
this.form = new ControlGroup({}, null, composeValidators(validators),
composeAsyncValidators(asyncValidators));
}
get formDirective(): Form { return this; }

View File

@ -7,9 +7,15 @@ import {Query, Directive} from 'angular2/src/core/metadata';
import {forwardRef, Provider, Inject, Optional} from 'angular2/src/core/di';
import {NgControl} from './ng_control';
import {Control} from '../model';
import {Validators, NG_VALIDATORS} from '../validators';
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {setUpControl, composeValidators, isPropertyUpdated, selectValueAccessor} from './shared';
import {
setUpControl,
composeValidators,
composeAsyncValidators,
isPropertyUpdated,
selectValueAccessor
} from './shared';
const formControlBinding =
CONST_EXPR(new Provider(NgControl, {useExisting: forwardRef(() => NgFormControl)}));
@ -73,13 +79,13 @@ export class NgFormControl extends NgControl implements OnChanges {
update = new EventEmitter();
model: any;
viewModel: any;
private _validator: Function;
constructor(@Optional() @Inject(NG_VALIDATORS) validators:
constructor(@Optional() @Inject(NG_VALIDATORS) private _validators:
/* Array<Validator|Function> */ any[],
@Optional() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
/* Array<Validator|Function> */ any[],
@Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
super();
this._validator = composeValidators(validators);
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
@ -96,7 +102,9 @@ export class NgFormControl extends NgControl implements OnChanges {
get path(): string[] { return []; }
get validator(): Function { return this._validator; }
get validator(): Function { return composeValidators(this._validators); }
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
get control(): Control { return this.form; }

View File

@ -11,8 +11,8 @@ import {NgControlGroup} from './ng_control_group';
import {ControlContainer} from './control_container';
import {Form} from './form_interface';
import {Control, ControlGroup} from '../model';
import {setUpControl, setUpControlGroup} from './shared';
import {Validators, NG_VALIDATORS} from '../validators';
import {setUpControl, setUpControlGroup, composeValidators, composeAsyncValidators} from './shared';
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
const formDirectiveProvider =
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgFormModel)}));
@ -102,17 +102,21 @@ export class NgFormModel extends ControlContainer implements Form,
form: ControlGroup = null;
directives: NgControl[] = [];
ngSubmit = new EventEmitter();
private _validators: Function[];
constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
constructor(@Optional() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
super();
this._validators = validators;
}
onChanges(changes: {[key: string]: SimpleChange}): void {
if (StringMapWrapper.contains(changes, "form")) {
var c = Validators.compose(this._validators);
this.form.validator = Validators.compose([this.form.validator, c]);
var sync = composeValidators(this._validators);
this.form.validator = Validators.compose([this.form.validator, sync]);
var async = composeAsyncValidators(this._asyncValidators);
this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]);
this.form.updateValueAndValidity({onlySelf: true, emitEvent: false});
}
this._updateDomValue();

View File

@ -7,8 +7,14 @@ import {forwardRef, Provider, Inject, Optional} from 'angular2/src/core/di';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {NgControl} from './ng_control';
import {Control} from '../model';
import {Validators, NG_VALIDATORS} from '../validators';
import {setUpControl, isPropertyUpdated, selectValueAccessor, composeValidators} from './shared';
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {
setUpControl,
isPropertyUpdated,
selectValueAccessor,
composeValidators,
composeAsyncValidators
} from './shared';
const formControlBinding =
CONST_EXPR(new Provider(NgControl, {useExisting: forwardRef(() => NgModel)}));
@ -49,13 +55,11 @@ export class NgModel extends NgControl implements OnChanges {
update = new EventEmitter();
model: any;
viewModel: any;
private _validator: Function;
constructor(@Optional() @Inject(NG_VALIDATORS) validators:
/* Array<Validator|Function> */ any[],
constructor(@Optional() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
@Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
super();
this._validator = composeValidators(validators);
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
@ -76,7 +80,9 @@ export class NgModel extends NgControl implements OnChanges {
get path(): string[] { return []; }
get validator(): Function { return this._validator; }
get validator(): Function { return composeValidators(this._validators); }
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;

View File

@ -29,6 +29,7 @@ export function setUpControl(control: Control, dir: NgControl): void {
if (isBlank(dir.valueAccessor)) _throwError(dir, "No value accessor for");
control.validator = Validators.compose([control.validator, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
dir.valueAccessor.writeValue(control.value);
// view -> model
@ -48,6 +49,7 @@ export function setUpControl(control: Control, dir: NgControl): void {
export function setUpControlGroup(control: ControlGroup, dir: NgControlGroup) {
if (isBlank(control)) _throwError(dir, "Cannot find control");
control.validator = Validators.compose([control.validator, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
}
function _throwError(dir: AbstractControlDirective, message: string): void {
@ -61,8 +63,12 @@ export function setProperty(renderer: Renderer, elementRef: ElementRef, propName
}
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): Function {
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) :
Validators.nullValidator;
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
}
export function composeAsyncValidators(
validators: /* Array<Validator|Function> */ any[]): Function {
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeValidator)) : null;
}
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {

View File

@ -142,8 +142,9 @@ export abstract class AbstractControl {
if (isPresent(this.asyncValidator)) {
this._status = PENDING;
this._cancelExistingSubscription();
var obs = ObservableWrapper.fromPromise(this.asyncValidator(this));
this._asyncValidationSubscription =
ObservableWrapper.subscribe(this.asyncValidator(this), res => this.setErrors(res));
ObservableWrapper.subscribe(obs, res => this.setErrors(res));
}
}