Skip to content

Commit

Permalink
feat(forms): add support for adding async validators via template
Browse files Browse the repository at this point in the history
Example:

@directive({
  selector: '[uniq-login-validator]',
  providers: [provide(NG_ASYNC_VALIDATORS, {useExisting: UniqLoginValidator, multi: true})]
})
class UniqLoginValidator implements Validator {
  validate(c) { return someFunctionReturningPromiseOrObservable(); }
}
  • Loading branch information
vsavkin committed Nov 2, 2015
1 parent cf449dd commit 31c12af
Show file tree
Hide file tree
Showing 14 changed files with 249 additions and 101 deletions.
2 changes: 1 addition & 1 deletion modules/angular2/src/core/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions modules/angular2/src/core/forms/directives/ng_control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
14 changes: 7 additions & 7 deletions modules/angular2/src/core/forms/directives/ng_control_group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}));
Expand Down Expand Up @@ -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); }
Expand All @@ -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); }
}
27 changes: 16 additions & 11 deletions modules/angular2/src/core/forms/directives/ng_control_name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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);
}

Expand All @@ -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); }
}
10 changes: 6 additions & 4 deletions modules/angular2/src/core/forms/directives/ng_form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}));
Expand Down Expand Up @@ -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; }
Expand Down
20 changes: 14 additions & 6 deletions modules/angular2/src/core/forms/directives/ng_form_control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}));
Expand Down Expand Up @@ -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);
}

Expand All @@ -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; }

Expand Down
18 changes: 11 additions & 7 deletions modules/angular2/src/core/forms/directives/ng_form_model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}));
Expand Down Expand Up @@ -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();
Expand Down
20 changes: 13 additions & 7 deletions modules/angular2/src/core/forms/directives/ng_model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}));
Expand Down Expand Up @@ -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);
}

Expand All @@ -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;
Expand Down
10 changes: 8 additions & 2 deletions modules/angular2/src/core/forms/directives/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion modules/angular2/src/core/forms/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down
Loading

0 comments on commit 31c12af

Please sign in to comment.