feat(forms): add support for async validations
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
library angular2.core.facade.promise;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:async' as async;
|
||||
export 'dart:async' show Future;
|
||||
|
||||
class PromiseWrapper {
|
||||
@ -29,6 +30,10 @@ class PromiseWrapper {
|
||||
return promise.catchError(onError);
|
||||
}
|
||||
|
||||
static void scheduleMicrotask(fn) {
|
||||
async.scheduleMicrotask(fn);
|
||||
}
|
||||
|
||||
static PromiseCompleter<dynamic> completer() =>
|
||||
new PromiseCompleter(new Completer());
|
||||
}
|
||||
|
@ -40,6 +40,10 @@ export class PromiseWrapper {
|
||||
});
|
||||
}
|
||||
|
||||
static scheduleMicrotask(computation: () => any): void {
|
||||
PromiseWrapper.then(PromiseWrapper.resolve(null), computation, (_) => {});
|
||||
}
|
||||
|
||||
static completer(): PromiseCompleter<any> {
|
||||
var resolve;
|
||||
var reject;
|
||||
|
@ -105,7 +105,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }
|
||||
|
||||
addControl(dir: NgControl): void {
|
||||
this._later(_ => {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
var ctrl = new Control();
|
||||
setUpControl(ctrl, dir);
|
||||
@ -117,7 +117,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||
getControl(dir: NgControl): Control { return <Control>this.form.find(dir.path); }
|
||||
|
||||
removeControl(dir: NgControl): void {
|
||||
this._later(_ => {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
@ -127,7 +127,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||
}
|
||||
|
||||
addControlGroup(dir: NgControlGroup): void {
|
||||
this._later(_ => {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
var group = new ControlGroup({});
|
||||
setUpControlGroup(group, dir);
|
||||
@ -137,7 +137,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||
}
|
||||
|
||||
removeControlGroup(dir: NgControlGroup): void {
|
||||
this._later(_ => {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
@ -151,7 +151,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||
}
|
||||
|
||||
updateModel(dir: NgControl, value: any): void {
|
||||
this._later(_ => {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var ctrl = <Control>this.form.find(dir.path);
|
||||
ctrl.updateValue(value);
|
||||
});
|
||||
@ -167,7 +167,4 @@ export class NgForm extends ControlContainer implements Form {
|
||||
path.pop();
|
||||
return ListWrapper.isEmpty(path) ? this.form : <ControlGroup>this.form.find(path);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_later(fn): void { PromiseWrapper.then(PromiseWrapper.resolve(null), fn, (_) => {}); }
|
||||
}
|
||||
|
@ -59,8 +59,9 @@ export abstract class AbstractControl {
|
||||
private _pristine: boolean = true;
|
||||
private _touched: boolean = false;
|
||||
private _parent: ControlGroup | ControlArray;
|
||||
private _asyncValidationSubscription;
|
||||
|
||||
constructor(public validator: Function) {}
|
||||
constructor(public validator: Function, public asyncValidator: Function) {}
|
||||
|
||||
get value(): any { return this._value; }
|
||||
|
||||
@ -119,10 +120,14 @@ export abstract class AbstractControl {
|
||||
|
||||
this._updateValue();
|
||||
|
||||
this._errors = this.validator(this);
|
||||
this._errors = this._runValidator();
|
||||
this._controlsErrors = this._calculateControlsErrors();
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (this._status == VALID || this._status == PENDING) {
|
||||
this._runAsyncValidator();
|
||||
}
|
||||
|
||||
if (emitEvent) {
|
||||
ObservableWrapper.callNext(this._valueChanges, this._value);
|
||||
}
|
||||
@ -132,6 +137,23 @@ export abstract class AbstractControl {
|
||||
}
|
||||
}
|
||||
|
||||
private _runValidator() { return isPresent(this.validator) ? this.validator(this) : null; }
|
||||
|
||||
private _runAsyncValidator() {
|
||||
if (isPresent(this.asyncValidator)) {
|
||||
this._status = PENDING;
|
||||
this._cancelExistingSubscription();
|
||||
this._asyncValidationSubscription =
|
||||
ObservableWrapper.subscribe(this.asyncValidator(this), res => this.setErrors(res));
|
||||
}
|
||||
}
|
||||
|
||||
private _cancelExistingSubscription(): void {
|
||||
if (isPresent(this._asyncValidationSubscription)) {
|
||||
ObservableWrapper.dispose(this._asyncValidationSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets errors on a control.
|
||||
*
|
||||
@ -190,13 +212,18 @@ export abstract class AbstractControl {
|
||||
}
|
||||
|
||||
private _calculateStatus(): string {
|
||||
return isPresent(this._errors) || isPresent(this._controlsErrors) ? INVALID : VALID;
|
||||
if (isPresent(this._errors)) return INVALID;
|
||||
if (this._anyControlsHaveStatus(PENDING)) return PENDING;
|
||||
if (this._anyControlsHaveStatus(INVALID)) return INVALID;
|
||||
return VALID;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
abstract _updateValue(): void;
|
||||
/** @internal */
|
||||
abstract _calculateControlsErrors(): any;
|
||||
/** @internal */
|
||||
abstract _anyControlsHaveStatus(status: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,8 +246,8 @@ export class Control extends AbstractControl {
|
||||
/** @internal */
|
||||
_onChange: Function;
|
||||
|
||||
constructor(value: any = null, validator: Function = Validators.nullValidator) {
|
||||
super(validator);
|
||||
constructor(value: any = null, validator: Function = null, asyncValidator: Function = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._value = value;
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
this._valueChanges = new EventEmitter();
|
||||
@ -259,6 +286,11 @@ export class Control extends AbstractControl {
|
||||
*/
|
||||
_calculateControlsErrors() { return null; }
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_anyControlsHaveStatus(status: string): boolean { return false; }
|
||||
|
||||
/**
|
||||
* Register a listener for change events.
|
||||
*/
|
||||
@ -282,9 +314,9 @@ export class ControlGroup extends AbstractControl {
|
||||
private _optionals: {[key: string]: boolean};
|
||||
|
||||
constructor(public controls: {[key: string]: AbstractControl},
|
||||
optionals: {[key: string]: boolean} = null,
|
||||
validator: Function = Validators.nullValidator) {
|
||||
super(validator);
|
||||
optionals: {[key: string]: boolean} = null, validator: Function = null,
|
||||
asyncValidator: Function = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._optionals = isPresent(optionals) ? optionals : {};
|
||||
this._valueChanges = new EventEmitter();
|
||||
|
||||
@ -348,6 +380,15 @@ export class ControlGroup extends AbstractControl {
|
||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_anyControlsHaveStatus(status: string): boolean {
|
||||
var res = false;
|
||||
StringMapWrapper.forEach(this.controls, (control, name) => {
|
||||
res = res || (this.contains(name) && control.status == status);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceValue() {
|
||||
return this._reduceChildren({}, (acc, control, name) => {
|
||||
@ -396,8 +437,9 @@ export class ControlGroup extends AbstractControl {
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*/
|
||||
export class ControlArray extends AbstractControl {
|
||||
constructor(public controls: AbstractControl[], validator: Function = Validators.nullValidator) {
|
||||
super(validator);
|
||||
constructor(public controls: AbstractControl[], validator: Function = null,
|
||||
asyncValidator: Function = null) {
|
||||
super(validator, asyncValidator);
|
||||
|
||||
this._valueChanges = new EventEmitter();
|
||||
|
||||
@ -457,6 +499,12 @@ export class ControlArray extends AbstractControl {
|
||||
return anyErrors ? res : null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_anyControlsHaveStatus(status: string): boolean {
|
||||
return ListWrapper.any(this.controls, c => c.status == status);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
_setParentForControls(): void {
|
||||
this.controls.forEach((control) => { control.setParent(this); });
|
||||
|
Reference in New Issue
Block a user