From 6c7300c7de9eb81a63a06c90ff43444ed1a19cb2 Mon Sep 17 00:00:00 2001 From: Dzmitry Shylovich Date: Sun, 4 Dec 2016 01:17:09 +0300 Subject: [PATCH] fix(forms): async validator cancels previous subscription when input has changed (#13222) Fixes #12709 Fixes #9120 Fixes #10074 Fixes #8923 PR Close #13222 --- modules/@angular/forms/src/model.ts | 2 +- .../forms/test/reactive_integration_spec.ts | 27 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/modules/@angular/forms/src/model.ts b/modules/@angular/forms/src/model.ts index 958d3775d5..53bb76604b 100644 --- a/modules/@angular/forms/src/model.ts +++ b/modules/@angular/forms/src/model.ts @@ -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})}); diff --git a/modules/@angular/forms/test/reactive_integration_spec.ts b/modules/@angular/forms/test/reactive_integration_spec.ts index 79587ea696..1a6c0f9fa2 100644 --- a/modules/@angular/forms/test/reactive_integration_spec.ts +++ b/modules/@angular/forms/test/reactive_integration_spec.ts @@ -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; }; }