Angular Material表格新增行后原有行值丢失问题求助
Angular Material表格新增行后已有输入值清空问题解决
问题描述
使用Angular Material构建表格,点击新增按钮可添加新行,但新增行后,其他行已输入的值会被清空。日志显示数据模型中的值是正确的,但界面上已输入的内容消失。
模板代码
<table id="options-table" mat-table [dataSource]="options" class="mat-elevation-z8"> <ng-container matColumnDef="option-name"> <th mat-header-cell *matHeaderCellDef>Option name</th> <td mat-cell *matCellDef="let option; let rowIndex = index"> <mat-form-field> <input matInput [(ngModel)]="options[rowIndex].optionName" placeholder="Enter a name for the option" [formControl]="optionNameFormControl"> <mat-error *ngIf="optionNameFormControl.hasError('required')"> Option name is <strong>required</strong> </mat-error> </mat-form-field> </td> </ng-container> <!-- ... more columns ... --> <tr mat-header-row *matHeaderRowDef="optionTableColumns"></tr> <tr mat-row *matRowDef="let row; columns: optionTableColumns;"></tr> </table> <button mat-raised-button color="primary" (click)="onAddOption()">Add an Option</button>
组件代码
import { Component, OnInit } from '@angular/core'; import { OptionGroup } from '../option-group'; import { Option} from '../option'; import { Location } from '@angular/common'; import { FormControl, ValidatorFn, Validators } from '@angular/forms'; import {ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-start-new-decision', templateUrl: './start-new-decision.component.html', styleUrls: ['./start-new-decision.component.scss'] }) export class StartNewDecisionComponent implements OnInit { options: Option[] = [{"optionName" : '', 'description': ''}]; optionNameFormControl = new FormControl('', [Validators.required]); optionTableColumns: string[] = ['option-name'/*, more properties */]; constructor(private location: Location, private cdref: ChangeDetectorRef) { } ngOnInit(): void { } onAddOption(): void { let optionToAdd: Option = { optionName: '', description: '' }; console.log("Option to add: " + JSON.stringify(optionToAdd)); console.log("Options before add: " + JSON.stringify(this.options)); this.options.push(optionToAdd); console.log("Options after add: " + JSON.stringify(this.options)); // Unfortunately material table requires a full copy to recognize the change. TODO: see if there is another way of doing it. this.options = [...this.options]; console.log("Options after copy: " + JSON.stringify(this.options)); this.cdref.detectChanges(); } }
操作现象
- 初始表格状态:仅一行空输入框
- 点击“Add an Option”后:新增一行空输入框
- 将第二行的option name改为“test”后:第二行显示“test”
- 再次点击“Add an Option”:第二行的“test”值消失,变回空输入框
日志输出(数据模型正常)
start-new-decision.component.ts:54 Option to add: {"optionName":"","description":""} start-new-decision.component.ts:55 Options before add: [{"optionName":"","description":""}] start-new-decision.component.ts:57 Options after add: [{"optionName":"","description":""},{"optionName":"","description":""}] start-new-decision.component.ts:60 Options after copy: [{"optionName":"","description":""},{"optionName":"","description":""}] start-new-decision.component.ts:54 Option to add: {"optionName":"","description":""} start-new-decision.component.ts:55 Options before add: [{"optionName":"","description":""},{"optionName":"test","description":""}] start-new-decision.component.ts:57 Options after add: [{"optionName":"","description":""},{"optionName":"test","description":""},{"optionName":"","description":""}] start-new-decision.component.ts:60 Options after copy: [{"optionName":"","description":""},{"optionName":"test","description":""},{"optionName":"","description":""}]
问题原因
所有表格行共用了同一个optionNameFormControl实例。当新增行触发表格重新渲染时,每个输入框都会绑定到这个唯一的FormControl,而它的初始值是空字符串,因此会覆盖界面上已输入的内容。虽然数据模型中的optionName值正确,但表单控件的状态会优先控制输入框的显示内容。
解决方案
方案1:使用FormArray管理每行表单(推荐)
这种方式更符合Angular表单的最佳实践,适合复杂场景:
- 组件中重构表单逻辑
import { Component, OnInit } from '@angular/core'; import { Option } from '../option'; import { Location } from '@angular/common'; import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms'; import { ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-start-new-decision', templateUrl: './start-new-decision.component.html', styleUrls: ['./start-new-decision.component.scss'] }) export class StartNewDecisionComponent implements OnInit { optionsForm: FormGroup; optionTableColumns: string[] = ['option-name', 'description']; // 根据实际列补充 constructor( private location: Location, private cdref: ChangeDetectorRef, private fb: FormBuilder // 注入FormBuilder ) { // 初始化FormArray,包含一个空的选项表单组 this.optionsForm = this.fb.group({ options: this.fb.array([this.createOptionFormGroup()]) }); } ngOnInit(): void { } // 获取FormArray的便捷方法 get optionsArray(): FormArray { return this.optionsForm.get('options') as FormArray; } // 创建单个选项的表单组 createOptionFormGroup(): FormGroup { return this.fb.group({ optionName: ['', Validators.required], description: [''] }); } onAddOption(): void { this.optionsArray.push(this.createOptionFormGroup()); } }
- 修改模板绑定
<table id="options-table" mat-table [dataSource]="optionsArray.controls" class="mat-elevation-z8"> <ng-container matColumnDef="option-name"> <th mat-header-cell *matHeaderCellDef>Option name</th> <td mat-cell *matCellDef="let optionForm; let rowIndex = index"> <mat-form-field> <input matInput [formControl]="optionForm.get('optionName')" placeholder="Enter a name for the option"> <mat-error *ngIf="optionForm.get('optionName').invalid && optionForm.get('optionName').touched"> Option name is <strong>required</strong> </mat-error> </mat-form-field> </td> </ng-container> <!-- description列示例 --> <ng-container matColumnDef="description"> <th mat-header-cell *matHeaderCellDef>Description</th> <td mat-cell *matCellDef="let optionForm; let rowIndex = index"> <mat-form-field> <input matInput [formControl]="optionForm.get('description')" placeholder="Enter description"> </mat-form-field> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="optionTableColumns"></tr> <tr mat-row *matRowDef="let row; columns: optionTableColumns;"></tr> </table> <button mat-raised-button color="primary" (click)="onAddOption()">Add an Option</button>
方案2:为每行单独创建FormControl(简单场景适用)
如果表单逻辑简单,可直接为每行的输入框分配独立的FormControl:
- 修改Option接口
export interface Option { optionName: string; description: string; optionNameControl: FormControl; // 添加FormControl属性 }
- 组件中初始化和新增逻辑
// 初始化options数组 options: Option[] = [{ optionName: '', description: '', optionNameControl: new FormControl('', Validators.required) }]; // 修改onAddOption方法 onAddOption(): void { let optionToAdd: Option = { optionName: '', description: '', optionNameControl: new FormControl('', Validators.required) }; this.options = [...this.options, optionToAdd]; }
- 修改模板绑定
<input matInput [(ngModel)]="option.optionName" placeholder="Enter a name for the option" [formControl]="option.optionNameControl"> <mat-error *ngIf="option.optionNameControl.hasError('required') && option.optionNameControl.touched"> Option name is <strong>required</strong> </mat-error>
内容的提问来源于stack exchange,提问作者hyperion
相关产品推荐
相关产品推荐

