feat(forms) range values need to be numbers instead of strings (#11792)
This commit is contained in:
		
							parent
							
								
									f77ab6a2d2
								
							
						
					
					
						commit
						0e9503b500
					
				| @ -16,6 +16,7 @@ import {NgModel} from './directives/ng_model'; | ||||
| import {NgModelGroup} from './directives/ng_model_group'; | ||||
| import {NumberValueAccessor} from './directives/number_value_accessor'; | ||||
| import {RadioControlValueAccessor} from './directives/radio_control_value_accessor'; | ||||
| import {RangeValueAccessor} from './directives/range_value_accessor'; | ||||
| import {FormControlDirective} from './directives/reactive_directives/form_control_directive'; | ||||
| import {FormControlName} from './directives/reactive_directives/form_control_name'; | ||||
| import {FormGroupDirective} from './directives/reactive_directives/form_group_directive'; | ||||
| @ -34,6 +35,7 @@ export {NgModel} from './directives/ng_model'; | ||||
| export {NgModelGroup} from './directives/ng_model_group'; | ||||
| export {NumberValueAccessor} from './directives/number_value_accessor'; | ||||
| export {RadioControlValueAccessor} from './directives/radio_control_value_accessor'; | ||||
| export {RangeValueAccessor} from './directives/range_value_accessor'; | ||||
| export {FormControlDirective} from './directives/reactive_directives/form_control_directive'; | ||||
| export {FormControlName} from './directives/reactive_directives/form_control_name'; | ||||
| export {FormGroupDirective} from './directives/reactive_directives/form_group_directive'; | ||||
| @ -44,9 +46,9 @@ export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValida | ||||
| 
 | ||||
| export const SHARED_FORM_DIRECTIVES: Type<any>[] = [ | ||||
|   NgSelectOption, NgSelectMultipleOption, DefaultValueAccessor, NumberValueAccessor, | ||||
|   CheckboxControlValueAccessor, SelectControlValueAccessor, SelectMultipleControlValueAccessor, | ||||
|   RadioControlValueAccessor, NgControlStatus, NgControlStatusGroup, RequiredValidator, | ||||
|   MinLengthValidator, MaxLengthValidator, PatternValidator | ||||
|   RangeValueAccessor, CheckboxControlValueAccessor, SelectControlValueAccessor, | ||||
|   SelectMultipleControlValueAccessor, RadioControlValueAccessor, NgControlStatus, | ||||
|   NgControlStatusGroup, RequiredValidator, MinLengthValidator, MaxLengthValidator, PatternValidator | ||||
| ]; | ||||
| 
 | ||||
| export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] = [NgModel, NgModelGroup, NgForm]; | ||||
|  | ||||
| @ -0,0 +1,57 @@ | ||||
| /** | ||||
|  * @license | ||||
|  * Copyright Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Use of this source code is governed by an MIT-style license that can be | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {Directive, ElementRef, Provider, Renderer, forwardRef} from '@angular/core'; | ||||
| 
 | ||||
| import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; | ||||
| 
 | ||||
| export const RANGE_VALUE_ACCESSOR: Provider = { | ||||
|   provide: NG_VALUE_ACCESSOR, | ||||
|   useExisting: forwardRef(() => RangeValueAccessor), | ||||
|   multi: true | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * The accessor for writing a range value and listening to changes that is used by the | ||||
|  * {@link NgModel}, {@link FormControlDirective}, and {@link FormControlName} directives. | ||||
|  * | ||||
|  *  ### Example | ||||
|  *  ``` | ||||
|  *  <input type="range" [(ngModel)]="age" > | ||||
|  *  ``` | ||||
|  */ | ||||
| @Directive({ | ||||
|   selector: | ||||
|       'input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]', | ||||
|   host: { | ||||
|     '(change)': 'onChange($event.target.value)', | ||||
|     '(input)': 'onChange($event.target.value)', | ||||
|     '(blur)': 'onTouched()' | ||||
|   }, | ||||
|   providers: [RANGE_VALUE_ACCESSOR] | ||||
| }) | ||||
| export class RangeValueAccessor implements ControlValueAccessor { | ||||
|   onChange = (_: any) => {}; | ||||
|   onTouched = () => {}; | ||||
| 
 | ||||
|   constructor(private _renderer: Renderer, private _elementRef: ElementRef) {} | ||||
| 
 | ||||
|   writeValue(value: any): void { | ||||
|     this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', parseFloat(value)); | ||||
|   } | ||||
| 
 | ||||
|   registerOnChange(fn: (_: number) => void): void { | ||||
|     this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; | ||||
|   } | ||||
| 
 | ||||
|   registerOnTouched(fn: () => void): void { this.onTouched = fn; } | ||||
| 
 | ||||
|   setDisabledState(isDisabled: boolean): void { | ||||
|     this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled); | ||||
|   } | ||||
| } | ||||
| @ -22,6 +22,7 @@ import {NgControl} from './ng_control'; | ||||
| import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator'; | ||||
| import {NumberValueAccessor} from './number_value_accessor'; | ||||
| import {RadioControlValueAccessor} from './radio_control_value_accessor'; | ||||
| import {RangeValueAccessor} from './range_value_accessor'; | ||||
| import {FormArrayName} from './reactive_directives/form_group_name'; | ||||
| import {SelectControlValueAccessor} from './select_control_value_accessor'; | ||||
| import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor'; | ||||
| @ -130,6 +131,7 @@ export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any) | ||||
| export function isBuiltInAccessor(valueAccessor: ControlValueAccessor): boolean { | ||||
|   return ( | ||||
|       hasConstructor(valueAccessor, CheckboxControlValueAccessor) || | ||||
|       hasConstructor(valueAccessor, RangeValueAccessor) || | ||||
|       hasConstructor(valueAccessor, NumberValueAccessor) || | ||||
|       hasConstructor(valueAccessor, SelectControlValueAccessor) || | ||||
|       hasConstructor(valueAccessor, SelectMultipleControlValueAccessor) || | ||||
|  | ||||
| @ -22,11 +22,26 @@ export function main() { | ||||
|       TestBed.configureTestingModule({ | ||||
|         imports: [FormsModule, ReactiveFormsModule], | ||||
|         declarations: [ | ||||
|           FormControlComp, FormGroupComp, FormArrayComp, FormArrayNestedGroup, | ||||
|           FormControlNameSelect, FormControlNumberInput, FormControlRadioButtons, WrappedValue, | ||||
|           WrappedValueForm, MyInput, MyInputForm, FormGroupNgModel, FormControlNgModel, | ||||
|           LoginIsEmptyValidator, LoginIsEmptyWrapper, ValidationBindingsForm, UniqLoginValidator, | ||||
|           UniqLoginWrapper, NestedFormGroupComp | ||||
|           FormControlComp, | ||||
|           FormGroupComp, | ||||
|           FormArrayComp, | ||||
|           FormArrayNestedGroup, | ||||
|           FormControlNameSelect, | ||||
|           FormControlNumberInput, | ||||
|           FormControlRangeInput, | ||||
|           FormControlRadioButtons, | ||||
|           WrappedValue, | ||||
|           WrappedValueForm, | ||||
|           MyInput, | ||||
|           MyInputForm, | ||||
|           FormGroupNgModel, | ||||
|           FormControlNgModel, | ||||
|           LoginIsEmptyValidator, | ||||
|           LoginIsEmptyWrapper, | ||||
|           ValidationBindingsForm, | ||||
|           UniqLoginValidator, | ||||
|           UniqLoginWrapper, | ||||
|           NestedFormGroupComp | ||||
|         ] | ||||
|       }); | ||||
|     }); | ||||
| @ -1126,6 +1141,57 @@ export function main() { | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|       describe('should support <type=range>', () => { | ||||
|         it('with basic use case', () => { | ||||
|           const fixture = TestBed.createComponent(FormControlRangeInput); | ||||
|           const control = new FormControl(10); | ||||
|           fixture.componentInstance.control = control; | ||||
|           fixture.detectChanges(); | ||||
| 
 | ||||
|           // model -> view
 | ||||
|           const input = fixture.debugElement.query(By.css('input')); | ||||
|           expect(input.nativeElement.value).toEqual('10'); | ||||
| 
 | ||||
|           input.nativeElement.value = '20'; | ||||
|           dispatchEvent(input.nativeElement, 'input'); | ||||
| 
 | ||||
|           // view -> model
 | ||||
|           expect(control.value).toEqual(20); | ||||
|         }); | ||||
| 
 | ||||
|         it('when value is cleared in the UI', () => { | ||||
|           const fixture = TestBed.createComponent(FormControlNumberInput); | ||||
|           const control = new FormControl(10, Validators.required); | ||||
|           fixture.componentInstance.control = control; | ||||
|           fixture.detectChanges(); | ||||
| 
 | ||||
|           const input = fixture.debugElement.query(By.css('input')); | ||||
|           input.nativeElement.value = ''; | ||||
|           dispatchEvent(input.nativeElement, 'input'); | ||||
| 
 | ||||
|           expect(control.valid).toBe(false); | ||||
|           expect(control.value).toEqual(null); | ||||
| 
 | ||||
|           input.nativeElement.value = '0'; | ||||
|           dispatchEvent(input.nativeElement, 'input'); | ||||
| 
 | ||||
|           expect(control.valid).toBe(true); | ||||
|           expect(control.value).toEqual(0); | ||||
|         }); | ||||
| 
 | ||||
|         it('when value is cleared programmatically', () => { | ||||
|           const fixture = TestBed.createComponent(FormControlNumberInput); | ||||
|           const control = new FormControl(10); | ||||
|           fixture.componentInstance.control = control; | ||||
|           fixture.detectChanges(); | ||||
| 
 | ||||
|           control.setValue(null); | ||||
| 
 | ||||
|           const input = fixture.debugElement.query(By.css('input')); | ||||
|           expect(input.nativeElement.value).toEqual(''); | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       describe('custom value accessors', () => { | ||||
|         it('should support basic functionality', () => { | ||||
|           const fixture = TestBed.createComponent(WrappedValueForm); | ||||
| @ -1852,6 +1918,16 @@ class FormControlNumberInput { | ||||
|   control: FormControl; | ||||
| } | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'form-control-range-input', | ||||
|   template: ` | ||||
|     <input type="range" [formControl]="control"> | ||||
|   ` | ||||
| }) | ||||
| class FormControlRangeInput { | ||||
|   control: FormControl; | ||||
| } | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'form-control-radio-buttons', | ||||
|   template: ` | ||||
|  | ||||
| @ -20,7 +20,7 @@ export function main() { | ||||
|       TestBed.configureTestingModule({ | ||||
|         declarations: [ | ||||
|           StandaloneNgModel, NgModelForm, NgModelGroupForm, NgModelValidBinding, NgModelNgIfForm, | ||||
|           NgModelRadioForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName, | ||||
|           NgModelRadioForm, NgModelRangeForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName, | ||||
|           NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper, | ||||
|           NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator, | ||||
|           NgModelAsyncValidation | ||||
| @ -503,6 +503,26 @@ export function main() { | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|     describe('range control', () => { | ||||
|       it('should support <type=range>', fakeAsync(() => { | ||||
|            const fixture = TestBed.createComponent(NgModelRangeForm); | ||||
|            // model -> view
 | ||||
|            fixture.componentInstance.val = 4; | ||||
|            fixture.detectChanges(); | ||||
|            tick(); | ||||
|            let input = fixture.debugElement.query(By.css('input')); | ||||
|            expect(input.nativeElement.value).toBe('4'); | ||||
|            fixture.detectChanges(); | ||||
|            tick(); | ||||
|            let newVal = '4'; | ||||
|            input.triggerEventHandler('input', {target: {value: newVal}}); | ||||
|            tick(); | ||||
|            // view -> model
 | ||||
|            fixture.detectChanges(); | ||||
|            expect(typeof(fixture.componentInstance.val)).toBe('number'); | ||||
|          })); | ||||
|     }); | ||||
| 
 | ||||
|     describe('radio controls', () => { | ||||
|       it('should support <type=radio>', fakeAsync(() => { | ||||
|            const fixture = TestBed.createComponent(NgModelRadioForm); | ||||
| @ -1023,6 +1043,11 @@ class NgModelOptionsStandalone { | ||||
|   two: string; | ||||
| } | ||||
| 
 | ||||
| @Component({selector: 'ng-model-range-form', template: '<input type="range" [(ngModel)]="val">'}) | ||||
| class NgModelRangeForm { | ||||
|   val: any; | ||||
| } | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'ng-model-radio-form', | ||||
|   template: ` | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user