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:
parent
cf449ddaa9
commit
31c12af81f
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ import {
|
|||
afterEach,
|
||||
el,
|
||||
AsyncTestCompleter,
|
||||
inject
|
||||
inject,
|
||||
tick
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {SpyNgControl, SpyValueAccessor} from '../spies';
|
||||
|
@ -38,7 +39,8 @@ import {
|
|||
|
||||
|
||||
import {selectValueAccessor, composeValidators} from 'angular2/src/core/forms/directives/shared';
|
||||
|
||||
import {TimerWrapper} from 'angular2/src/core/facade/async';
|
||||
import {PromiseWrapper} from 'angular2/src/core/facade/promise';
|
||||
import {SimpleChange} from 'angular2/src/core/change_detection';
|
||||
|
||||
class DummyControlValueAccessor implements ControlValueAccessor {
|
||||
|
@ -54,6 +56,19 @@ class CustomValidatorDirective implements Validator {
|
|||
validate(c: Control): {[key: string]: any} { return {"custom": true}; }
|
||||
}
|
||||
|
||||
function asyncValidator(expected, timeout = 0) {
|
||||
return (c) => {
|
||||
var completer = PromiseWrapper.completer();
|
||||
var res = c.value != expected ? {"async": true} : null;
|
||||
if (timeout == 0) {
|
||||
completer.resolve(res);
|
||||
} else {
|
||||
TimerWrapper.setTimeout(() => { completer.resolve(res); }, timeout);
|
||||
}
|
||||
return completer.promise;
|
||||
};
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe("Form Directives", () => {
|
||||
var defaultAccessor;
|
||||
|
@ -125,7 +140,7 @@ export function main() {
|
|||
var loginControlDir;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgFormModel([]);
|
||||
form = new NgFormModel([], []);
|
||||
formModel = new ControlGroup({
|
||||
"login": new Control(),
|
||||
"passwords":
|
||||
|
@ -133,7 +148,8 @@ export function main() {
|
|||
});
|
||||
form.form = formModel;
|
||||
|
||||
loginControlDir = new NgControlName(form, [Validators.required], [defaultAccessor]);
|
||||
loginControlDir = new NgControlName(form, [Validators.required],
|
||||
[asyncValidator("expected")], [defaultAccessor]);
|
||||
loginControlDir.name = "login";
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
@ -151,7 +167,7 @@ export function main() {
|
|||
|
||||
describe("addControl", () => {
|
||||
it("should throw when no control found", () => {
|
||||
var dir = new NgControlName(form, null, [defaultAccessor]);
|
||||
var dir = new NgControlName(form, null, null, [defaultAccessor]);
|
||||
dir.name = "invalidName";
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
|
@ -159,21 +175,30 @@ export function main() {
|
|||
});
|
||||
|
||||
it("should throw when no value accessor", () => {
|
||||
var dir = new NgControlName(form, null, null);
|
||||
var dir = new NgControlName(form, null, null, null);
|
||||
dir.name = "login";
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp("No value accessor for 'login'"));
|
||||
});
|
||||
|
||||
it("should set up validator", () => {
|
||||
expect(formModel.find(["login"]).valid).toBe(true);
|
||||
|
||||
// this will add the required validator and recalculate the validity
|
||||
it("should set up validators", fakeAsync(() => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
expect(formModel.find(["login"]).valid).toBe(false);
|
||||
});
|
||||
// sync validators are set
|
||||
expect(formModel.hasError("required", ["login"])).toBe(true);
|
||||
expect(formModel.hasError("async", ["login"])).toBe(false);
|
||||
|
||||
formModel.find(["login"]).updateValue("invalid value");
|
||||
|
||||
// sync validator passes, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError("required", ["login"])).toBe(false);
|
||||
expect(formModel.hasError("async", ["login"])).toBe(true);
|
||||
}));
|
||||
|
||||
it("should write value to the DOM", () => {
|
||||
formModel.find(["login"]).updateValue("initValue");
|
||||
|
@ -198,15 +223,27 @@ export function main() {
|
|||
}
|
||||
};
|
||||
|
||||
it("should set up validator", () => {
|
||||
var group = new NgControlGroup(form, [matchingPasswordsValidator]);
|
||||
it("should set up validator", fakeAsync(() => {
|
||||
var group = new NgControlGroup(form, [matchingPasswordsValidator],
|
||||
[asyncValidator('expected')]);
|
||||
group.name = "passwords";
|
||||
form.addControlGroup(group);
|
||||
|
||||
formModel.find(["passwords", "password"]).updateValue("somePassword");
|
||||
formModel.find(["passwords", "passwordConfirm"]).updateValue("someOtherPassword");
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError("differentPasswords", ["passwords"])).toEqual(true);
|
||||
});
|
||||
|
||||
formModel.find(["passwords", "passwordConfirm"]).updateValue("somePassword");
|
||||
|
||||
// sync validators pass, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError("async", ["passwords"])).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe("removeControl", () => {
|
||||
|
@ -228,17 +265,24 @@ export function main() {
|
|||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual("new value");
|
||||
});
|
||||
|
||||
it("should set up validator", () => {
|
||||
it("should set up a sync validator", () => {
|
||||
var formValidator = (c) => ({"custom": true});
|
||||
var f = new NgFormModel([formValidator]);
|
||||
var f = new NgFormModel([formValidator], []);
|
||||
f.form = formModel;
|
||||
f.onChanges({"form": formModel});
|
||||
|
||||
// trigger validation
|
||||
formModel.controls["login"].updateValue("");
|
||||
|
||||
expect(formModel.errors).toEqual({"custom": true});
|
||||
});
|
||||
|
||||
it("should set up an async validator", fakeAsync(() => {
|
||||
var f = new NgFormModel([], [asyncValidator("expected")]);
|
||||
f.form = formModel;
|
||||
f.onChanges({"form": formModel});
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.errors).toEqual({"async": true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -249,13 +293,13 @@ export function main() {
|
|||
var personControlGroupDir;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgForm([]);
|
||||
form = new NgForm([], []);
|
||||
formModel = form.form;
|
||||
|
||||
personControlGroupDir = new NgControlGroup(form, []);
|
||||
personControlGroupDir = new NgControlGroup(form, [], []);
|
||||
personControlGroupDir.name = "person";
|
||||
|
||||
loginControlDir = new NgControlName(personControlGroupDir, null, [defaultAccessor]);
|
||||
loginControlDir = new NgControlName(personControlGroupDir, null, null, [defaultAccessor]);
|
||||
loginControlDir.name = "login";
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
@ -301,16 +345,22 @@ export function main() {
|
|||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
it("should set up validator", fakeAsync(() => {
|
||||
it("should set up sync validator", fakeAsync(() => {
|
||||
var formValidator = (c) => ({"custom": true});
|
||||
var f = new NgForm([formValidator]);
|
||||
f.addControlGroup(personControlGroupDir);
|
||||
f.addControl(loginControlDir);
|
||||
var f = new NgForm([formValidator], []);
|
||||
|
||||
flushMicrotasks();
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({"custom": true});
|
||||
}));
|
||||
|
||||
it("should set up async validator", fakeAsync(() => {
|
||||
var f = new NgForm([], [asyncValidator("expected")]);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({"async": true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe("NgControlGroup", () => {
|
||||
|
@ -320,9 +370,9 @@ export function main() {
|
|||
beforeEach(() => {
|
||||
formModel = new ControlGroup({"login": new Control(null)});
|
||||
|
||||
var parent = new NgFormModel([]);
|
||||
var parent = new NgFormModel([], []);
|
||||
parent.form = new ControlGroup({"group": formModel});
|
||||
controlGroupDir = new NgControlGroup(parent, []);
|
||||
controlGroupDir = new NgControlGroup(parent, [], []);
|
||||
controlGroupDir.name = "group";
|
||||
});
|
||||
|
||||
|
@ -353,7 +403,7 @@ export function main() {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
controlDir = new NgFormControl([Validators.required], [defaultAccessor]);
|
||||
controlDir = new NgFormControl([Validators.required], [], [defaultAccessor]);
|
||||
controlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
|
||||
control = new Control(null);
|
||||
|
@ -384,7 +434,8 @@ export function main() {
|
|||
var ngModel;
|
||||
|
||||
beforeEach(() => {
|
||||
ngModel = new NgModel([Validators.required], [defaultAccessor]);
|
||||
ngModel =
|
||||
new NgModel([Validators.required], [asyncValidator("expected")], [defaultAccessor]);
|
||||
ngModel.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
|
@ -400,14 +451,18 @@ export function main() {
|
|||
expect(ngModel.untouched).toBe(control.untouched);
|
||||
});
|
||||
|
||||
it("should set up validator", () => {
|
||||
expect(ngModel.control.valid).toBe(true);
|
||||
|
||||
it("should set up validator", fakeAsync(() => {
|
||||
// this will add the required validator and recalculate the validity
|
||||
ngModel.onChanges({});
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.valid).toBe(false);
|
||||
});
|
||||
expect(ngModel.control.errors).toEqual({"required": true});
|
||||
|
||||
ngModel.control.updateValue("someValue");
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({"async": true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe("NgControlName", () => {
|
||||
|
@ -417,9 +472,9 @@ export function main() {
|
|||
beforeEach(() => {
|
||||
formModel = new Control("name");
|
||||
|
||||
var parent = new NgFormModel([]);
|
||||
var parent = new NgFormModel([], []);
|
||||
parent.form = new ControlGroup({"name": formModel});
|
||||
controlNameDir = new NgControlName(parent, [], [defaultAccessor]);
|
||||
controlNameDir = new NgControlName(parent, [], [], [defaultAccessor]);
|
||||
controlNameDir.name = "name";
|
||||
});
|
||||
|
||||
|
|
|
@ -20,11 +20,13 @@ import {
|
|||
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {
|
||||
Input,
|
||||
Control,
|
||||
ControlGroup,
|
||||
ControlValueAccessor,
|
||||
FORM_DIRECTIVES,
|
||||
NG_VALIDATORS,
|
||||
NG_ASYNC_VALIDATORS,
|
||||
Provider,
|
||||
NgControl,
|
||||
NgIf,
|
||||
|
@ -401,7 +403,7 @@ export function main() {
|
|||
});
|
||||
|
||||
describe("validations", () => {
|
||||
it("should use validators defined in html",
|
||||
it("should use sync validators defined in html",
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var form = new ControlGroup(
|
||||
{"login": new Control(""), "min": new Control(""), "max": new Control("")});
|
||||
|
@ -446,6 +448,35 @@ export function main() {
|
|||
});
|
||||
}));
|
||||
|
||||
it("should use async validators defined in the html",
|
||||
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
|
||||
var form = new ControlGroup({"login": new Control("")});
|
||||
|
||||
var t = `<div [ng-form-model]="form">
|
||||
<input type="text" ng-control="login" uniq-login-validator="expected">
|
||||
</div>`;
|
||||
|
||||
var rootTC;
|
||||
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((root) => rootTC = root);
|
||||
tick();
|
||||
|
||||
rootTC.debugElement.componentInstance.form = form;
|
||||
rootTC.detectChanges();
|
||||
|
||||
expect(form.pending).toEqual(true);
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(form.hasError("uniqLogin", ["login"])).toEqual(true);
|
||||
|
||||
var input = rootTC.debugElement.query(By.css("input"));
|
||||
input.nativeElement.value = "expected";
|
||||
dispatchEvent(input.nativeElement, "change");
|
||||
tick(100);
|
||||
|
||||
expect(form.valid).toEqual(true);
|
||||
})));
|
||||
|
||||
it("should use sync validators defined in the model",
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var form = new ControlGroup({"login": new Control("aa", Validators.required)});
|
||||
|
@ -961,10 +992,10 @@ class MyInput implements ControlValueAccessor {
|
|||
|
||||
function uniqLoginAsyncValidator(expectedValue: string) {
|
||||
return (c) => {
|
||||
var e = new EventEmitter();
|
||||
var completer = PromiseWrapper.completer();
|
||||
var res = (c.value == expectedValue) ? null : {"uniqLogin": true};
|
||||
PromiseWrapper.scheduleMicrotask(() => ObservableWrapper.callNext(e, res));
|
||||
return e;
|
||||
completer.resolve(res);
|
||||
return completer.promise;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -979,10 +1010,31 @@ function loginIsEmptyGroupValidator(c: ControlGroup) {
|
|||
class LoginIsEmptyValidator {
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[uniq-login-validator]',
|
||||
providers: [
|
||||
new Provider(NG_ASYNC_VALIDATORS,
|
||||
{useExisting: forwardRef(() => UniqLoginValidator), multi: true})
|
||||
]
|
||||
})
|
||||
class UniqLoginValidator implements Validator {
|
||||
@Input('uniq-login-validator') expected;
|
||||
|
||||
validate(c) { return uniqLoginAsyncValidator(this.expected)(c); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "my-comp",
|
||||
template: '',
|
||||
directives: [FORM_DIRECTIVES, WrappedValue, MyInput, NgIf, NgFor, LoginIsEmptyValidator]
|
||||
directives: [
|
||||
FORM_DIRECTIVES,
|
||||
WrappedValue,
|
||||
MyInput,
|
||||
NgIf,
|
||||
NgFor,
|
||||
LoginIsEmptyValidator,
|
||||
UniqLoginValidator
|
||||
]
|
||||
})
|
||||
class MyComp {
|
||||
form: any;
|
||||
|
|
|
@ -15,23 +15,24 @@ import {
|
|||
} from 'angular2/testing_internal';
|
||||
import {ControlGroup, Control, ControlArray, Validators} from 'angular2/core';
|
||||
import {isPresent, CONST_EXPR} from 'angular2/src/core/facade/lang';
|
||||
import {EventEmitter, TimerWrapper, ObservableWrapper} from 'angular2/src/core/facade/async';
|
||||
import {PromiseWrapper} from 'angular2/src/core/facade/promise';
|
||||
import {TimerWrapper, ObservableWrapper} from 'angular2/src/core/facade/async';
|
||||
import {IS_DART} from '../../platform';
|
||||
import {PromiseWrapper} from "angular2/src/core/facade/promise";
|
||||
|
||||
export function main() {
|
||||
function asyncValidator(expected, timeouts = CONST_EXPR({})) {
|
||||
return (c) => {
|
||||
var e = new EventEmitter();
|
||||
var completer = PromiseWrapper.completer();
|
||||
var t = isPresent(timeouts[c.value]) ? timeouts[c.value] : 0;
|
||||
var res = c.value != expected ? {"async": true} : null;
|
||||
|
||||
if (t == 0) {
|
||||
PromiseWrapper.scheduleMicrotask(() => { ObservableWrapper.callNext(e, res); });
|
||||
completer.resolve(res);
|
||||
} else {
|
||||
TimerWrapper.setTimeout(() => { ObservableWrapper.callNext(e, res); }, t);
|
||||
TimerWrapper.setTimeout(() => { completer.resolve(res); }, t);
|
||||
}
|
||||
return e;
|
||||
|
||||
return completer.promise;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -819,6 +819,7 @@ var NG_ALL = [
|
|||
'LowerCasePipe',
|
||||
'LowerCasePipe.transform()',
|
||||
'NG_VALIDATORS',
|
||||
'NG_ASYNC_VALIDATORS',
|
||||
'NgClass',
|
||||
'NgClass.doCheck()',
|
||||
'NgClass.initialClasses=',
|
||||
|
@ -837,6 +838,7 @@ var NG_ALL = [
|
|||
'NgControl.untouched',
|
||||
'NgControl.valid',
|
||||
'NgControl.validator',
|
||||
'NgControl.asyncValidator',
|
||||
'NgControl.value',
|
||||
'NgControl.valueAccessor',
|
||||
'NgControl.valueAccessor=',
|
||||
|
@ -857,6 +859,7 @@ var NG_ALL = [
|
|||
'NgControlGroup.valid',
|
||||
'NgControlGroup.value',
|
||||
'NgControlGroup.validator',
|
||||
'NgControlGroup.asyncValidator',
|
||||
'NgControlStatus',
|
||||
'NgControlStatus.ngClassDirty',
|
||||
'NgControlStatus.ngClassInvalid',
|
||||
|
@ -884,6 +887,7 @@ var NG_ALL = [
|
|||
'NgControlName.update=',
|
||||
'NgControlName.valid',
|
||||
'NgControlName.validator',
|
||||
'NgControlName.asyncValidator',
|
||||
'NgControlName.value',
|
||||
'NgControlName.valueAccessor',
|
||||
'NgControlName.valueAccessor=',
|
||||
|
@ -941,6 +945,7 @@ var NG_ALL = [
|
|||
'NgFormControl.update=',
|
||||
'NgFormControl.valid',
|
||||
'NgFormControl.validator',
|
||||
'NgFormControl.asyncValidator',
|
||||
'NgFormControl.value',
|
||||
'NgFormControl.valueAccessor',
|
||||
'NgFormControl.valueAccessor=',
|
||||
|
@ -996,6 +1001,7 @@ var NG_ALL = [
|
|||
'NgModel.update=',
|
||||
'NgModel.valid',
|
||||
'NgModel.validator',
|
||||
'NgModel.asyncValidator',
|
||||
'NgModel.value',
|
||||
'NgModel.valueAccessor',
|
||||
'NgModel.valueAccessor=',
|
||||
|
@ -1223,6 +1229,7 @@ var NG_ALL = [
|
|||
'UrlResolver',
|
||||
'UrlResolver.resolve()',
|
||||
'Validators#compose()',
|
||||
'Validators#composeAsync()',
|
||||
'Validators#nullValidator()',
|
||||
'Validators#required()',
|
||||
'Validators#minLength()',
|
||||
|
|
Loading…
Reference in New Issue