angular表单,动态表单结合nz-table布局

316 阅读2分钟

先说大致流程

  • 使用group群组表单里面放置array数组表单,形成嵌套表单
    • 通过往array数组表单里面添加群组表单,实现动态添加
    • 通过往array数组表单里面移除群组表单,实现动态删除
  • 通过将array数组表单的数据,当作nz-table的表体
  • 基本代码
// component.html
<form [formGroup]="myForm">
    <nz-table #table [nzData]="items.controls" nzBordered class="dynamicTable" [nzScroll]="{y:'300px'}">
        <thead>
            <tr>
                <th nzWidth="40%">Name</th>
                <th nzWidth="40%">Age</th>
                <th nzWidth="20%">oper</th>
            </tr>
        </thead>
        <tbody *ngIf="items.controls.length>0" formArrayName="item">
            <tr *ngFor="let item of items.controls; let i=index" [formGroupName]="i">
                <td>
                    <nz-input-group>
                        <input nz-input formControlName="name" placeholder="Name">
                        <div *ngIf="hasRequiredError('name', i)" class="error-message text-red">Name is required
                        </div>
                    </nz-input-group>
                </td>
                <td>
                    <nz-input-group>
                        <input nz-input formControlName="age" placeholder="Age">
                        <div *ngIf="hasRequiredError('age', i)" class="error-message text-red">Age is required
                        </div>
                    </nz-input-group>
                </td>
                <td>
                    <button nz-button (click)="removeItem(i)">Remove</button>
                </td>
            </tr>
        </tbody>
    </nz-table>
</form>
<button nz-button nzType="primary" (click)="addItem()">Add Item</button>
<--检验不通过时也会禁用按钮-->
<button nz-button nzType="primary" (click)="submitForm()"
    [disabled]="items.controls.length<1 || myForm.invalid">Submit</button>

// .less
// 隐藏空白内容
.dynamicTable ::ng-deep .ant-table-placeholder {
    display: none
}

// component.ts
import { Component, NgZone, TemplateRef, ViewChild } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { SharedModule } from '@shared';

@Component({
  selector: 'app-dymaic-table',
  templateUrl: './dymaic-table.component.html',
  styleUrls: ['./dymaic-table.component.less'],
  standalone: true,
  imports: [SharedModule]
})
export class DymaicTableComponent {
  @ViewChild('table') table!: TemplateRef<{}>;
  myForm: FormGroup;
  constructor(private fb: FormBuilder, private ngZone: NgZone) {
    this.myForm = this.fb.group({
      item: this.fb.array([])
    });
  }
  removeItem(index: number) {
    this.items.removeAt(index);
  }
  addItem() {
    this.items.push(this.createItem());
    const elBody = this.table.elementRef.nativeElement.querySelector('.ant-table-body')
    if (elBody) {
      this.ngZone.runOutsideAngular(() => {
        requestAnimationFrame(() => {
          elBody.scrollTop = elBody.scrollHeight;
          // 添加平滑滚动
          // elBody.scrollTo({ top: elBody.scrollHeight, behavior: 'smooth' })
        });
      });
    }
  }
  get items() {
    return (this.myForm.controls['item'] || []) as FormArray
  }
  createItem(): FormGroup {
    return this.fb.group({
      name: ['', Validators.required],
      age: ['', Validators.required]
    });
  }
  // 展示检验不通过的错误提示信息,写在组件里面是为了绕过没有ts声明,防止ts编译失败
  hasRequiredError(controlName: string, index: number): boolean {
    const control = this.items.at(index).get(controlName);
    return control && control.touched && control.errors?.['required'];
  }
  submitForm() {
    if (this.myForm.valid) {
      // 执行表单提交的逻辑
      console.log('Form submitted:', this.myForm.value);
    } else {
      // 表单验证失败时的处理
      console.log('Form is invalid');
    }
  }
}

缺陷完善

  • elBody.scrollHeight, elBody.offsetHeight始终相等。判断不了高度
addItem() {
    this.items.push(this.createItem());
    const elBody = this.table.elementRef.nativeElement.querySelector('.ant-table-body')
    if (elBody && elBody.parentElement.offsetHeight > parseFloat(getComputedStyle(elBody).maxHeight)) {
      this.ngZone.runOutsideAngular(() => {
        requestAnimationFrame(() => {
          elBody.scrollTop = elBody.scrollHeight;
          // 添加平滑滚动
          // elBody.scrollTo({ top: elBody.scrollHeight, behavior: 'smooth' })
        });
      });
    }
}

改成分页方式

  • 需要手动实现分页
// component.html
<form [formGroup]="myForm">
    <nz-table #table [nzData]="currentPageData" nzBordered class="dynamicTable" [nzShowPagination]="false">
        <thead>
            <tr>
                <th nzWidth="10%">序号</th>
                <th nzWidth="40%">Name</th>
                <th nzWidth="40%">Age</th>
                <th nzWidth="10%">oper</th>
            </tr>
        </thead>
        <tbody *ngIf="items.controls.length>0" formArrayName="item">
            <tr *ngFor="let item of table.data; let i=index" [formGroupName]="i+(page - 1) * pageSize">
                <td>
                    {{i+1}}
                </td>
                <td>
                    <nz-input-group>
                        <input nz-input formControlName="name" placeholder="Name">
                        <div *ngIf="hasRequiredError('name', i)" class="error-message text-red">Name is required
                        </div>
                    </nz-input-group>
                </td>
                <td>
                    <nz-input-group>
                        <input nz-input formControlName="age" placeholder="Age">
                        <div *ngIf="hasRequiredError('age', i)" class="error-message text-red">Age is required
                        </div>
                    </nz-input-group>
                </td>
                <td>
                    <button nz-button (click)="removeItem(i)">Remove</button>
                </td>
            </tr>
        </tbody>
    </nz-table>
    <div *ngIf="items.controls.length>0" class="d-flex justify-content-end mt-lg">
        <div class="d-flex align-items-center mr-md">共{{items.controls.length}}条</div>
        <nz-pagination [nzPageIndex]="page" [nzPageSize]="pageSize" [nzTotal]="items.controls.length"
            (nzPageIndexChange)="changePage($event)"></nz-pagination>
    </div>
</form>
<div class="modal-footer">
    <button nz-button nzType="primary" (click)="addItem()">Add Item</button>
    <button nz-button nzType="primary" [disabled]="items.controls.length<1 || myForm.invalid"
        (click)="submitForm()">Submit</button>
</div>

// component.ts
import { Component, NgZone, TemplateRef, ViewChild } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { STComponent } from '@delon/abc/st';
import { SharedModule } from '@shared';

@Component({
  selector: 'app-dymaic-table',
  templateUrl: './dymaic-table.component.html',
  styleUrls: ['./dymaic-table.component.less'],
  standalone: true,
  imports: [SharedModule]
})
export class DymaicTableComponent {
  @ViewChild('table') table!: STComponent;
  myForm: FormGroup;
  constructor(private fb: FormBuilder, private ngZone: NgZone) {
    this.myForm = this.fb.group({
      item: this.fb.array([])
    });
  }
  page = 1
  pageSize = 5
  get currentPageData() {
    const startIndex = (this.page - 1) * this.pageSize;
    return this.items.controls.slice(startIndex, startIndex + this.pageSize);
  }
  changePage(val: any): void {
    console.log(this.items.value)
    this.page = val
  }
  removeItem(index: number) {
    this.items.removeAt(index);
  }
  addItem() {
    const field = this.createItem()
    field.updateValueAndValidity
    this.items.push(field);
    this.table.data = this.items.controls
    if (this.items.controls.length > this.page * this.pageSize) {
      this.page = this.page + 1
    }
  }
  get items() {
    return (this.myForm.controls['item'] || []) as FormArray
  }
  createItem(): FormGroup {
    return this.fb.group({
      name: ['', Validators.required],
      age: ['', Validators.required]
    });
  }
  // 展示检验不通过的错误提示信息,写在组件里面是为了绕过没有ts声明,防止ts编译失败
  hasRequiredError(controlName: string, index: number): boolean {
    const control = this.items.at(index).get(controlName);
    return control && control.touched && control.errors?.['required'];
  }
  submitForm() {
    if (this.myForm.valid) {
      // 执行表单提交的逻辑
      console.log('Form submitted:', this.myForm.value);
    } else {
      // 表单验证失败时的处理
      console.log('Form is invalid');
    }
  }
}