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

Angular 9多行动态表单:统一展示所有行的校验错误

解决方案:统一展示Angular动态表单的唯一校验错误

我来帮你实现把所有行的校验错误统一展示在表单下方且每个错误仅显示一次的需求!下面是具体的修改步骤:

第一步:在组件类中添加错误收集逻辑

首先修改app.component.ts,添加错误消息映射和去重错误的获取方法:

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'employeelist';
  buttonstatus = false;
  isSubmitted = '';
  submitted = false;
  userTable: FormGroup;
  control: FormArray;
  employeelist: any = [];
  selectedGroup: any;
  counts = [
    { "id": 1, "name": "One" },
    { "id": 2, "name": "Two" },
    { "id": 3, "name": "Three" },
    { "id": 4, "name": "Four" },
    { "id": 5, "name": "Five" }
  ];

  // 新增:统一管理所有字段的错误消息
  private errorMessages = {
    firstname: {
      required: 'First Name is required',
      minlength: 'First Name must be at least 3 characters'
    },
    lastname: {
      required: 'Last Name is required',
      minlength: 'Last Name must be at least 1 character'
    },
    email: {
      required: 'Email is required',
      pattern: 'Enter Valid Email'
    },
    dob: {
      required: 'Date of Birth is required'
    },
    mob: {
      required: 'Mobile no is required',
      pattern: 'Enter Valid Mobile No'
    }
  };

  constructor(private fb: FormBuilder) { }
  ngOnInit(): void {
    this.userTable = this.fb.group({
      tableRows: this.fb.array([])
    });
  }
  initiateForm(): FormGroup {
    return this.fb.group({
      firstname: ['', [Validators.required, Validators.minLength(3)]],
      lastname: ['', [Validators.required, Validators.minLength(1)]],
      email: ['', [Validators.required, Validators.pattern("^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$")]],
      dob: ['', [Validators.required]],
      mob: ['', [Validators.required, Validators.pattern("^((\\+91-?)|0)?[0-9]{10}$")]],
      isEditable: [false]
    });
  }
  get getFormControls() {
    const control = this.userTable.get('tableRows') as FormArray;
    return control;
  }

  // 新增:获取去重后的所有校验错误
  get uniqueValidationErrors(): string[] {
    const errorsSet = new Set<string>();
    const formArray = this.userTable.get('tableRows') as FormArray;

    formArray.controls.forEach(group => {
      // 遍历当前行的所有控件
      Object.keys(group.controls).forEach(controlName => {
        const control = group.get(controlName);
        // 只有当控件无效,且已提交或已触摸时才收集错误
        if (control && control.invalid && (this.submitted || control.touched)) {
          // 遍历控件的所有错误类型
          Object.keys(control.errors!).forEach(errorKey => {
            // 根据控件名和错误类型匹配对应的错误消息
            const message = this.errorMessages[controlName as keyof typeof this.errorMessages][errorKey as keyof typeof this.errorMessages[typeof controlName]];
            if (message) {
              errorsSet.add(message); // Set自动去重,相同错误只存一次
            }
          });
        }
      });
    });

    return Array.from(errorsSet); // 转成数组供模板使用
  }

  addRow() {
    if (this.selectedGroup) {
      let control = this.userTable.get('tableRows') as FormArray;
      var count = this.selectedGroup.id;
      for (let i = 0; i < count; i++) {
        control.push(this.initiateForm());
      }
      this.buttonstatus = true;
      this.isSubmitted = 'true';
    } else {
      this.isSubmitted = 'false';
    }
  }
  deleteRow(index: number) {
    const control = this.userTable.get('tableRows') as FormArray;
    control.removeAt(index);
    if (control.length == 0) {
      this.buttonstatus = false;
    }
  }
  doneRow(group: FormGroup) {
    if (group.valid) {
      group.get('isEditable').setValue(true);
    }
  }
  onSubmit() {
    this.submitted = true;
    if (this.userTable.invalid) {
      return;
    }
    // display form values on success
    alert('SUCCESS!! :-) \n' + JSON.stringify(this.userTable.value));
    this.employeelist = this.userTable.value;
  }
}

第二步:修改模板移除行内错误并添加统一错误展示

修改app.component.html,先删除每行字段下方的错误提示,然后在表单末尾添加统一错误展示区域:

<div class="container">
  <h1>Employee List</h1>
  <div class="card">
    <div class="card-header">
      Employee
      <div class="input-group mb-3">
        <select class="custom-select" [(ngModel)]="selectedGroup" [disabled]='buttonstatus'>
          <option *ngFor="let item of counts" [ngValue]="item">{{item.name}}</option>
        </select>
        <div class="input-group-append">
          <button (click)="addRow()" [disabled]='buttonstatus' class="btn btn-outline-secondary" type="button">
            GO
          </button>
        </div>
      </div>
      <div class="invalid" *ngIf="isSubmitted == 'false'">
        <sup>*</sup>Please select the no. of employees
      </div>
    </div>
    <div class="card-body">
      <form [formGroup]="userTable" (ngSubmit)="onSubmit()">
        <table class="table table-striped table-bordered">
          <thead>
            <tr>
              <th>#</th>
              <th>First Name</th>
              <th>Last Name</th>
              <th>Email</th>
              <th>DOB</th>
              <th>Mobile</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            <ng-container formArrayName="tableRows" *ngFor="let group of getFormControls.controls; let i=index">
              <tr [formGroupName]="i">
                <th>{{i+1}}</th>
                <td>
                  <input type="text" required formControlName="firstname" class="form-control input-sm" 
                    [ngClass]="{ 'is-invalid': (submitted || getFormControls.controls[i].get('firstname').touched) && getFormControls.controls[i].get('firstname').errors }" />
                  <!-- 移除行内错误提示 -->
                </td>
                <td>
                  <input type="text" required class="form-control input-sm" formControlName="lastname" 
                    [ngClass]="{ 'is-invalid': (submitted || getFormControls.controls[i].get('lastname').touched) && getFormControls.controls[i].get('lastname').errors }" />
                  <!-- 移除行内错误提示 -->
                </td>
                <td>
                  <input type="email" class="form-control input-sm" required formControlName="email" 
                    [ngClass]="{ 'is-invalid': (submitted || getFormControls.controls[i].get('email').touched) && getFormControls.controls[i].get('email').errors }" />
                  <!-- 移除行内错误提示 -->
                </td>
                <td>
                  <input type="date" class="form-control input-sm" required formControlName="dob" 
                    [ngClass]="{ 'is-invalid': (submitted || getFormControls.controls[i].get('dob').touched) && getFormControls.controls[i].get('dob').errors }" />
                  <!-- 移除行内错误提示 -->
                </td>
                <td>
                  <input type="tel" class="form-control input-sm" required formControlName="mob" 
                    [ngClass]="{ 'is-invalid': (submitted || getFormControls.controls[i].get('mob').touched) && getFormControls.controls[i].get('mob').errors }" />
                  <!-- 移除行内错误提示 -->
                </td>
                <td>
                  <input type="button" class="btn btn-outline-danger" value="Delete" (click)="deleteRow(i)" />
                </td>
              </tr>
            </ng-container>
          </tbody>
        </table>
        <button type="submit" class="btn btn-outline-primary" [disabled]='!buttonstatus'>Submit</button>

        <!-- 新增:统一展示所有唯一校验错误 -->
        <div class="alert alert-danger mt-3" *ngIf="uniqueValidationErrors.length > 0">
          <h6>Validation Errors:</h6>
          <ul class="mb-0">
            <li *ngFor="let error of uniqueValidationErrors">{{ error }}</li>
          </ul>
        </div>
      </form>
    </div>
  </div>
</div>

逻辑说明

  1. 错误消息映射:用errorMessages对象统一管理所有字段的错误提示,后续修改错误文案只需改这里,更易维护。
  2. 去重逻辑:使用Set来存储错误消息,因为Set的特性是不允许重复值,所以不管多少行有相同错误,只会保留一条。
  3. 触发条件:只有当控件无效,且表单已提交或控件已被触摸时,才会收集错误,和你原来的校验触发逻辑保持一致。
  4. 实时更新:uniqueValidationErrors是一个getter属性,每当表单状态变化时会自动重新计算,确保展示的错误是最新的。

这样修改后,就能实现你想要的效果:比如3行表单中,一行有firstName必填错误、一行有lastName和email错误,第三行无错误,统一展示为:

  • First Name is required
  • Last Name is required
  • Email is required

内容的提问来源于stack exchange,提问作者Merrin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 08:07:31