fix(forms): async validator cancels previous subscription when input has changed (#13222)

Fixes #12709
Fixes #9120
Fixes #10074
Fixes #8923

PR Close #13222
This commit is contained in:
Dzmitry Shylovich 2016-12-04 01:17:09 +03:00 committed by Miško Hevery
parent 22058298d3
commit 6c7300c7de
2 changed files with 26 additions and 3 deletions

View File

@ -384,6 +384,7 @@ export abstract class AbstractControl {
this._updateValue();
if (this.enabled) {
this._cancelExistingSubscription();
this._errors = this._runValidator();
this._status = this._calculateStatus();
@ -417,7 +418,6 @@ export abstract class AbstractControl {
private _runAsyncValidator(emitEvent: boolean): void {
if (this.asyncValidator) {
this._status = PENDING;
this._cancelExistingSubscription();
const obs = toObservable(this.asyncValidator(this));
this._asyncValidationSubscription =
obs.subscribe({next: (res: {[key: string]: any}) => this.setErrors(res, {emitEvent})});

View File

@ -1582,6 +1582,29 @@ export function main() {
expect(form.valid).toEqual(true);
}));
it('async validator should not override result of sync validator', fakeAsync(() => {
const fixture = initTest(FormGroupComp);
const control =
new FormControl('', Validators.required, uniqLoginAsyncValidator('expected', 100));
fixture.componentInstance.form = new FormGroup({'login': control});
fixture.detectChanges();
tick();
expect(control.hasError('required')).toEqual(true);
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.value = 'expected';
dispatchEvent(input.nativeElement, 'input');
expect(control.pending).toEqual(true);
input.nativeElement.value = '';
dispatchEvent(input.nativeElement, 'input');
tick(110);
expect(control.valid).toEqual(false);
}));
});
describe('errors', () => {
@ -1829,12 +1852,12 @@ class MyInput implements ControlValueAccessor {
dispatchChangeEvent() { this.onInput.emit(this.value.substring(1, this.value.length - 1)); }
}
function uniqLoginAsyncValidator(expectedValue: string) {
function uniqLoginAsyncValidator(expectedValue: string, timeout: number = 0) {
return (c: AbstractControl) => {
let resolve: (result: any) => void;
const promise = new Promise(res => { resolve = res; });
const res = (c.value == expectedValue) ? null : {'uniqLogin': true};
resolve(res);
setTimeout(() => resolve(res), timeout);
return promise;
};
}