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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user