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:
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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); }
|
||||
}
|
||||
|
@ -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); }
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user