Angular 18:监听FormArray内quantity FormControl的valueChanges并限制数字输入
解决方案:Angular中处理FormArray内数值输入的限制
要处理FormArray中quantity字段的数值限制,需要针对FormArray的每一行单独设置监听,同时结合输入事件拦截来阻止非数字输入,具体步骤如下:
1. 封装FormArray的获取方法
为了方便操作,先在组件中定义获取unitDetails FormArray的getter:
get unitDetails(): FormArray { return this.form.get('unitDetails') as FormArray; }
2. 为新增/已有行的quantity控件添加valueChanges监听
因为quantity属于FormArray内的每个FormGroup,所以需要给每一行的该控件单独设置监听,同时要注意避免内存泄漏:
2.1 封装监听逻辑为复用方法
import { Subject, takeUntil, distinctUntilChanged } from 'rxjs'; // 组件内定义销毁信号,用于取消订阅 private destroy$ = new Subject<void>(); private setupQuantityControlListener(group: FormGroup): void { const quantityControl = group.get('quantity'); if (!quantityControl) return; quantityControl.valueChanges .pipe( distinctUntilChanged(), // 避免重复处理相同值 takeUntil(this.destroy$) // 组件销毁时自动取消订阅 ) .subscribe((value) => { // 过滤非数字内容,保留数字和单个小数点(如需整数可去掉小数点相关逻辑) let filteredValue = value.replace(/[^0-9.]/g, ''); // 确保只有一个小数点 filteredValue = filteredValue.replace(/(\..*)\./g, '$1'); // 仅当过滤后的值与原不同时更新,避免循环触发valueChanges if (filteredValue !== value) { quantityControl.setValue(filteredValue, { emitEvent: false }); } }); }
2.2 在添加新行时绑定监听
假设你有添加行的方法addUnitRow,在创建FormGroup后立即绑定监听:
addUnitRow(): void { const unitGroup = this.fb.group({ unit: [''], quantity: [''], remarks: [''] }); // 给当前行的quantity绑定监听 this.setupQuantityControlListener(unitGroup); this.unitDetails.push(unitGroup); }
2.3 为初始化时的已有行绑定监听
如果组件初始化时unitDetails已有默认行,在ngOnInit中遍历绑定:
ngOnInit(): void { // 遍历已有行,绑定quantity监听 this.unitDetails.controls.forEach((group) => { this.setupQuantityControlListener(group as FormGroup); }); }
3. 阻止非数字输入(前端拦截)
除了valueChanges过滤,还可以通过输入事件直接阻止非数字按键,提升用户体验:
3.1 修改模板中的input元素
给quantity的input添加keydown事件:
<td><input class="form-control" formControlName="quantity" placeholder="Enter Quantity" (keydown)="onQuantityKeydown($event)" /></td>
3.2 实现事件处理方法
onQuantityKeydown(event: KeyboardEvent): void { // 允许的按键:数字、小数点、退格、删除、方向键、Tab const allowedKeys = ['0','1','2','3','4','5','6','7','8','9','.', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab']; if (!allowedKeys.includes(event.key)) { event.preventDefault(); return; } // 若输入框已有小数点,阻止再次输入小数点 const inputElement = event.target as HTMLInputElement; if (event.key === '.' && inputElement.value.includes('.')) { event.preventDefault(); } }
4. 组件销毁时清理订阅
在组件销毁时发送销毁信号,避免内存泄漏:
ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }
完整代码片段(组件部分)
import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, FormArray } from '@angular/forms'; import { Subject, takeUntil, distinctUntilChanged } from 'rxjs'; @Component({ // 组件元数据 }) export class YourComponent implements OnInit, OnDestroy { form: FormGroup; private destroy$ = new Subject<void>(); constructor(private fb: FormBuilder) { this.form = this.fb.group({ unitDetails: this.fb.array([]), discountDetails: this.fb.array([]), statusGroup: this.fb.group({ status: [''] }) }); } get unitDetails(): FormArray { return this.form.get('unitDetails') as FormArray; } ngOnInit(): void { // 初始化已有行的监听 this.unitDetails.controls.forEach((group) => { this.setupQuantityControlListener(group as FormGroup); }); } addUnitRow(): void { const unitGroup = this.fb.group({ unit: [''], quantity: [''], remarks: [''] }); this.setupQuantityControlListener(unitGroup); this.unitDetails.push(unitGroup); } removeUnitRow(index: number): void { this.unitDetails.removeAt(index); } private setupQuantityControlListener(group: FormGroup): void { const quantityControl = group.get('quantity'); if (!quantityControl) return; quantityControl.valueChanges .pipe( distinctUntilChanged(), takeUntil(this.destroy$) ) .subscribe((value) => { let filteredValue = value.replace(/[^0-9.]/g, ''); filteredValue = filteredValue.replace(/(\..*)\./g, '$1'); if (filteredValue !== value) { quantityControl.setValue(filteredValue, { emitEvent: false }); } }); } onQuantityKeydown(event: KeyboardEvent): void { const allowedKeys = ['0','1','2','3','4','5','6','7','8','9','.', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab']; if (!allowedKeys.includes(event.key)) { event.preventDefault(); return; } const inputElement = event.target as HTMLInputElement; if (event.key === '.' && inputElement.value.includes('.')) { event.preventDefault(); } } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }
内容的提问来源于stack exchange,提问作者ARUN PRAGASH
相关产品推荐
相关产品推荐

