feat(forms): add ng-pending CSS class during async validation (#11243)
Closes #10336
This commit is contained in:
parent
445e5922ec
commit
97bc97153b
|
@ -37,6 +37,9 @@ export class AbstractControlStatus {
|
|||
get ngClassInvalid(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.invalid : false;
|
||||
}
|
||||
get ngClassPending(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.pending : false;
|
||||
}
|
||||
}
|
||||
|
||||
export const ngControlStatusHost = {
|
||||
|
@ -45,7 +48,8 @@ export const ngControlStatusHost = {
|
|||
'[class.ng-pristine]': 'ngClassPristine',
|
||||
'[class.ng-dirty]': 'ngClassDirty',
|
||||
'[class.ng-valid]': 'ngClassValid',
|
||||
'[class.ng-invalid]': 'ngClassInvalid'
|
||||
'[class.ng-invalid]': 'ngClassInvalid',
|
||||
'[class.ng-pending]': 'ngClassPending'
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -646,6 +646,60 @@ export function main() {
|
|||
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
||||
});
|
||||
|
||||
it('should work with single fields and async validators', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const control = new FormControl('', null, uniqLoginAsyncValidator('good'));
|
||||
fixture.debugElement.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
fixture.detectChanges();
|
||||
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-touched']);
|
||||
|
||||
input.value = 'good';
|
||||
dispatchEvent(input, 'input');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
||||
}));
|
||||
|
||||
it('should work with single fields that combines async and sync validators', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const control =
|
||||
new FormControl('', Validators.required, uniqLoginAsyncValidator('good'));
|
||||
fixture.debugElement.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
fixture.detectChanges();
|
||||
expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
|
||||
|
||||
input.value = 'bad';
|
||||
dispatchEvent(input, 'input');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-pending', 'ng-touched']);
|
||||
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-invalid', 'ng-touched']);
|
||||
|
||||
input.value = 'good';
|
||||
dispatchEvent(input, 'input');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
||||
}));
|
||||
|
||||
it('should work with single fields in parent forms', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const form = new FormGroup({'login': new FormControl('', Validators.required)});
|
||||
|
@ -1736,7 +1790,7 @@ class LoginIsEmptyValidator {
|
|||
}]
|
||||
})
|
||||
class UniqLoginValidator implements Validator {
|
||||
@Input('uniq-login-validator') expected: any /** TODO #9100 */;
|
||||
@Input('uniq-login-validator') expected: any;
|
||||
|
||||
validate(c: AbstractControl) { return uniqLoginAsyncValidator(this.expected)(c); }
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {Component, Directive, Input, forwardRef} from '@angular/core';
|
||||
import {TestBed, async, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, NgForm} from '@angular/forms';
|
||||
import {AbstractControl, ControlValueAccessor, FormsModule, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, NgForm, Validator} from '@angular/forms';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
|
||||
|
@ -22,7 +22,8 @@ export function main() {
|
|||
StandaloneNgModel, NgModelForm, NgModelGroupForm, NgModelValidBinding, NgModelNgIfForm,
|
||||
NgModelRadioForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
|
||||
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
|
||||
NgModelValidationBindings, NgModelMultipleValidators
|
||||
NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator,
|
||||
NgModelAsyncValidation
|
||||
],
|
||||
imports: [FormsModule]
|
||||
});
|
||||
|
@ -139,7 +140,6 @@ export function main() {
|
|||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
|
@ -154,6 +154,29 @@ export function main() {
|
|||
});
|
||||
}));
|
||||
|
||||
it('should set status classes with ngModel and async validators', fakeAsync(() => {
|
||||
|
||||
const fixture = TestBed.createComponent(NgModelAsyncValidation);
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-touched']);
|
||||
|
||||
input.value = 'updatedValue';
|
||||
dispatchEvent(input, 'input');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should set status classes with ngModelGroup and ngForm', async(() => {
|
||||
const fixture = TestBed.createComponent(NgModelGroupForm);
|
||||
fixture.componentInstance.first = '';
|
||||
|
@ -883,7 +906,7 @@ export function main() {
|
|||
});
|
||||
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'standalone-ng-model',
|
||||
|
@ -1096,6 +1119,23 @@ class NgModelMultipleValidators {
|
|||
pattern: string;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ng-async-validator]',
|
||||
providers: [
|
||||
{provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => NgAsyncValidator), multi: true}
|
||||
]
|
||||
})
|
||||
class NgAsyncValidator implements Validator {
|
||||
validate(c: AbstractControl) { return Promise.resolve(null); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng-model-async-validation',
|
||||
template: `<input name="async" ngModel ng-async-validator>`
|
||||
})
|
||||
class NgModelAsyncValidation {
|
||||
}
|
||||
|
||||
function sortedClassList(el: HTMLElement) {
|
||||
const l = getDOM().classList(el);
|
||||
l.sort();
|
||||
|
|
Loading…
Reference in New Issue