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 {
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'
};
/**

View File

@ -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); }
}

View File

@ -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();