feat(forms): add ng-pending CSS class during async validation (#11243)

Closes #10336
This commit is contained in:
Pawel Kozlowski 2016-10-19 18:56:31 +02:00 committed by Alex Rickabaugh
parent 445e5922ec
commit 97bc97153b
3 changed files with 105 additions and 7 deletions

View File

@ -37,6 +37,9 @@ export class AbstractControlStatus {
get ngClassInvalid(): boolean { get ngClassInvalid(): boolean {
return isPresent(this._cd.control) ? this._cd.control.invalid : false; 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 = { export const ngControlStatusHost = {
@ -45,7 +48,8 @@ export const ngControlStatusHost = {
'[class.ng-pristine]': 'ngClassPristine', '[class.ng-pristine]': 'ngClassPristine',
'[class.ng-dirty]': 'ngClassDirty', '[class.ng-dirty]': 'ngClassDirty',
'[class.ng-valid]': 'ngClassValid', '[class.ng-valid]': 'ngClassValid',
'[class.ng-invalid]': 'ngClassInvalid' '[class.ng-invalid]': 'ngClassInvalid',
'[class.ng-pending]': 'ngClassPending'
}; };
/** /**

View File

@ -646,6 +646,60 @@ export function main() {
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); 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', () => { it('should work with single fields in parent forms', () => {
const fixture = TestBed.createComponent(FormGroupComp); const fixture = TestBed.createComponent(FormGroupComp);
const form = new FormGroup({'login': new FormControl('', Validators.required)}); const form = new FormGroup({'login': new FormControl('', Validators.required)});
@ -1736,7 +1790,7 @@ class LoginIsEmptyValidator {
}] }]
}) })
class UniqLoginValidator implements Validator { 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); } validate(c: AbstractControl) { return uniqLoginAsyncValidator(this.expected)(c); }
} }

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * 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 {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 {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util'; import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
@ -22,7 +22,8 @@ export function main() {
StandaloneNgModel, NgModelForm, NgModelGroupForm, NgModelValidBinding, NgModelNgIfForm, StandaloneNgModel, NgModelForm, NgModelGroupForm, NgModelValidBinding, NgModelNgIfForm,
NgModelRadioForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName, NgModelRadioForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper, NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
NgModelValidationBindings, NgModelMultipleValidators NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator,
NgModelAsyncValidation
], ],
imports: [FormsModule] imports: [FormsModule]
}); });
@ -139,7 +140,6 @@ export function main() {
fixture.detectChanges(); fixture.detectChanges();
const input = fixture.debugElement.query(By.css('input')).nativeElement; 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']); expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
dispatchEvent(input, 'blur'); 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(() => { it('should set status classes with ngModelGroup and ngForm', async(() => {
const fixture = TestBed.createComponent(NgModelGroupForm); const fixture = TestBed.createComponent(NgModelGroupForm);
fixture.componentInstance.first = ''; fixture.componentInstance.first = '';
@ -883,7 +906,7 @@ export function main() {
}); });
}); });
}; }
@Component({ @Component({
selector: 'standalone-ng-model', selector: 'standalone-ng-model',
@ -1096,6 +1119,23 @@ class NgModelMultipleValidators {
pattern: string; 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) { function sortedClassList(el: HTMLElement) {
const l = getDOM().classList(el); const l = getDOM().classList(el);
l.sort(); l.sort();