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:
parent
22058298d3
commit
6c7300c7de
@ -384,6 +384,7 @@ export abstract class AbstractControl {
|
|||||||
this._updateValue();
|
this._updateValue();
|
||||||
|
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
|
this._cancelExistingSubscription();
|
||||||
this._errors = this._runValidator();
|
this._errors = this._runValidator();
|
||||||
this._status = this._calculateStatus();
|
this._status = this._calculateStatus();
|
||||||
|
|
||||||
@ -417,7 +418,6 @@ export abstract class AbstractControl {
|
|||||||
private _runAsyncValidator(emitEvent: boolean): void {
|
private _runAsyncValidator(emitEvent: boolean): void {
|
||||||
if (this.asyncValidator) {
|
if (this.asyncValidator) {
|
||||||
this._status = PENDING;
|
this._status = PENDING;
|
||||||
this._cancelExistingSubscription();
|
|
||||||
const obs = toObservable(this.asyncValidator(this));
|
const obs = toObservable(this.asyncValidator(this));
|
||||||
this._asyncValidationSubscription =
|
this._asyncValidationSubscription =
|
||||||
obs.subscribe({next: (res: {[key: string]: any}) => this.setErrors(res, {emitEvent})});
|
obs.subscribe({next: (res: {[key: string]: any}) => this.setErrors(res, {emitEvent})});
|
||||||
|
@ -1582,6 +1582,29 @@ export function main() {
|
|||||||
expect(form.valid).toEqual(true);
|
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', () => {
|
describe('errors', () => {
|
||||||
@ -1829,12 +1852,12 @@ class MyInput implements ControlValueAccessor {
|
|||||||
dispatchChangeEvent() { this.onInput.emit(this.value.substring(1, this.value.length - 1)); }
|
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) => {
|
return (c: AbstractControl) => {
|
||||||
let resolve: (result: any) => void;
|
let resolve: (result: any) => void;
|
||||||
const promise = new Promise(res => { resolve = res; });
|
const promise = new Promise(res => { resolve = res; });
|
||||||
const res = (c.value == expectedValue) ? null : {'uniqLogin': true};
|
const res = (c.value == expectedValue) ? null : {'uniqLogin': true};
|
||||||
resolve(res);
|
setTimeout(() => resolve(res), timeout);
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user