You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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表单的最佳实践,适合复杂场景:

  1. 组件中重构表单逻辑
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());
  }
}
  1. 修改模板绑定
<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:

  1. 修改Option接口
export interface Option {
  optionName: string;
  description: string;
  optionNameControl: FormControl; // 添加FormControl属性
}
  1. 组件中初始化和新增逻辑
// 初始化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];
}
  1. 修改模板绑定
<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:15:40