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));
}
}

View File

@ -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";
});

View File

@ -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;

View File

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

View File

@ -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()',