Angular 12 Reactive Forms属性网格的校验与变更检测实现问题
问题1:从FormArray读取类型渲染对应控件
你已经知道formArray.controls是FormGroup数组,只需在模板循环时读取每个FormGroup的value即可拿到存储的type、name等元信息,实现要点:
- 组件类中将
PropertyTypeEnum枚举声明为public,模板才能直接访问枚举值 - *ngFor循环时用
as语法把当前FormGroup的value存为临时变量,简化模板取值 - 响应式表单不要和
[(ngModel)]混用,直接用formControlName绑定value字段即可
对应代码修改示例:
// 组件类顶部暴露枚举,模板才能访问 public PropertyTypeEnum = PropertyTypeEnum; // 存储初始表单值用于后续变更检测 private initialFormValue: any; ngOnInit(): void { this.settings.forEach(item => { // 动态生成对应类型的校验器 const validators = []; if (!item.isNullable) validators.push(Validators.required); switch(item.type) { case PropertyTypeEnum.String: validators.push(Validators.minLength(item.minValue ?? 2)); break; case PropertyTypeEnum.Int: validators.push(Validators.pattern(/^-?\d+$/)); item.minValue !== undefined && validators.push(Validators.min(item.minValue)); item.maxValue !== undefined && validators.push(Validators.max(item.maxValue)); break; } this.formArray.push(this.formBuilder.group({ key: [item.key], type: [item.type], name: [item.displayName], value: [item.value, validators], originalValue: [item.originalValue] })) }); // 存储初始值用于变更检测 this.initialFormValue = JSON.parse(JSON.stringify(this.form.value)); // 只需监听整个表单变化即可,无需重复监听formArray this.form.valueChanges.pipe(debounceTime(500)).subscribe(() => { // 深比较当前值和初始值判断是否有变更,替换你原来错误的判断逻辑 this.areChangesDetected = JSON.stringify(this.form.value) !== JSON.stringify(this.initialFormValue); }); } // 单个项重置方法 reset(index: number): void { const originalValue = this.formArray.at(index).get('originalValue')?.value; this.formArray.at(index).get('value')?.setValue(originalValue); }
模板修改示例:
<form [formGroup]="form" class="group-wrapper"> <div class="d-flex item-row" *ngFor="let group of formArray.controls; let i = index"> <!-- 把当前FormGroup的value存为setting变量,直接读取元信息 --> <ng-container *ngIf="group.value as setting"> <div class="item flex-fill d-flex" [ngSwitch]="setting.type" [formGroupName]="i"> <span>{{setting.name}}</span> <!-- 布尔类型下拉框 --> <select *ngSwitchCase="PropertyTypeEnum.Boolean" class="flex-grow-1" formControlName="value"> <option [value]="'0'">False</option> <option [value]="'1'">True</option> </select> <!-- 数字类型输入框 --> <input *ngSwitchCase="PropertyTypeEnum.Int" type="number" class="flex-grow-1" formControlName="value"> </div> </ng-container> <button class="remove-relation" (click)="reset(i)"> <fa-icon [icon]="faUndo"></fa-icon> </button> </div> </form>
问题2:contenteditable非标准控件的表单绑定
有两种实现方案,可根据复用需求选择:
方案1:简单事件绑定(无需额外定义指令)
直接监听div的输入、失焦事件,手动同步值到表单控件:
<div contenteditable *ngSwitchCase="PropertyTypeEnum.String" class="flex-grow-1" [id]="setting.key" [innerText]="group.get('value')?.value" (input)="onContentEditableChange($event, i)" ></div>
组件类新增处理方法:
onContentEditableChange(event: InputEvent, index: number): void { const inputValue = (event.target as HTMLElement).innerText.trim(); this.formArray.at(index).get('value')?.setValue(inputValue); }
方案2:自定义ControlValueAccessor指令(可复用,用法和原生输入框完全一致)
定义contenteditable适配指令:
@Directive({ selector: '[contenteditable][formControlName],[contenteditable][formControl]', standalone: true // 非独立模块移除该字段,添加到对应NgModule的declarations数组 }) export class ContenteditableValueAccessor implements ControlValueAccessor { constructor(private elementRef: ElementRef<HTMLElement>) {} onChange = (value: any) => {}; onTouched = () => {}; @HostListener('input', ['$event']) onInput(event: InputEvent): void { this.onChange((event.target as HTMLElement).innerText.trim()); } @HostListener('blur') onBlur(): void { this.onTouched(); } writeValue(value: any): void { this.elementRef.nativeElement.innerText = value ?? ''; } registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; } }
使用时直接给contenteditable的div加formControlName="value"即可,无需手动绑定事件:
<div contenteditable *ngSwitchCase="PropertyTypeEnum.String" class="flex-grow-1" formControlName="value" [id]="setting.key" ></div>
内容的提问来源于stack exchange,提问作者Nicke Manarin
相关产品推荐
相关产品推荐

