Permalink
Browse files

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

Closes #10336
  • Loading branch information...
pkozlowski-opensource authored and alxhub committed Oct 19, 2016
1 parent 445e592 commit 97bc97153b45afb5221eb4efe9d9ebae6fe0d81d
@@ -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();

0 comments on commit 97bc971

Please sign in to comment.