先说大致流程
- 使用group群组表单里面放置array数组表单,形成嵌套表单
- 通过往array数组表单里面添加群组表单,实现动态添加
- 通过往array数组表单里面移除群组表单,实现动态删除
- 通过将array数组表单的数据,当作nz-table的表体
- 基本代码
<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>
.dynamicTable ::ng-deep .ant-table-placeholder {
display: none
}
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;
});
});
}
}
get items() {
return (this.myForm.controls['item'] || []) as FormArray
}
createItem(): FormGroup {
return this.fb.group({
name: ['', Validators.required],
age: ['', Validators.required]
});
}
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;
});
});
}
}
改成分页方式
<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>
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]
});
}
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');
}
}
}