Angular新手求助:如何在Angular Material表格中渲染FormGroup并解决报错
Angular Material表格行过滤:FormGroup渲染报错修复
问题背景
我是Angular新手,想通过FormGroup在Angular Material表格里实现行数据过滤功能,更新模板后出现两个错误:
ERROR TypeError: control.registerOnChange is not a function ERROR TypeError: this.ngControl.control.hasValidator is not a function
我的实现代码
组件TS代码
@Component({ selector: 'app-grid-view', templateUrl: './grid-view.component.html', styleUrls: ['./grid-view.component.scss'] }) export class GridViewComponent implements OnInit, AfterViewInit { @Input() dataSource!: IndexDataSource; @Input() sortField: string = 'id' @Input() sortOrder: SortDirection = 'desc'; @Input() tableColumns: Array<IColumn> = []; @Input() model = ''; // Handle pagination property @ViewChild(MatPaginator, {static: false}) paginator!: MatPaginator; // Handle sorting property @ViewChild(MatSort, {static: false}) sort!: MatSort; // Handle column that will be displayed displayedColumns: Array<string> = []; // For handle row search by each column displayedColumnsSearch: Array<string> = []; fieldsOfFormSearch: Array<any> = []; // For filters, accommodate the filter inputs filterValues: { [key: string]: string } = {}; filterString!: string; // FormGroup to hold filter form controls formGroup: FormGroup = new FormGroup({}); @Output() filterChange = new EventEmitter<{ [key: string]: string }>(); ngOnInit(): void { // => Init rows based on param url let paramMap = this.dataSource.activatedRoute.snapshot.queryParamMap; this.dataSource.loadData( Number(paramMap.get('page')) || 1, Number(paramMap.get('per-page')) || 10, paramMap.get('sort') || this.sortField, paramMap.get('sort')?.startsWith("-") ? 'asc' : this.sortOrder, paramMap.get('filter') || this.filterString ); // => Column that will display in table this.displayedColumns = this.tableColumns.map((column) => column.columnDef); // => Form element that will be displayed in second row this.fieldsOfFormSearch = this.tableColumns.map((column) => ({ status: column.isFilterable !== undefined, formControl: column.columnDef + 'Search', urlSearch: this.model + `[${column.columnDef}]`, })); // => Create form controls for filter inputs and add them to the FormGroup this.fieldsOfFormSearch .filter((field) => field.status) .forEach((field) => { let formControl = new FormControl(); this.formGroup.addControl(field.urlSearch, formControl); formControl.valueChanges .pipe(debounceTime(300), distinctUntilChanged()) .subscribe((value: any) => { this.filterValues[field.urlSearch] = value; this.applyFilters(); }); }); // Column that will be display in table this.displayedColumnsSearch = this.fieldsOfFormSearch.map((c) => c.urlSearch); // Debug the form control console.log(this.formGroup); } applyFilters() { this.filterString = Object.keys(this.filterValues) .map((key) => `${key}=${this.filterValues[key]}`) .join('&'); this.dataSource.applyFilters(this.filterString); this.filterChange.emit(this.filterValues); } }
初始HTML模板代码
<!-- Actual Data --> <ng-container *ngFor="let column of tableColumns;" matColumnDef="{{column.columnDef}}"> <th mat-header-cell *matHeaderCellDef>{{column.header}}</th> <td mat-cell *matCellDef="let element">{{column.cell(element)}}</td> </ng-container> <!-- Filter Data --> <ng-container *ngFor="let field of fieldsOfFormSearch" matColumnDef="{{field.urlSearch}}" > <th mat-header-cell *matHeaderCellDef> <span *ngIf="!field.status; else displayField"></span> <ng-template #displayField> <!-- Filtering here--> A form control here how to render it ? </ng-template> </th> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matHeaderRowDef="displayedColumnsSearch"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
更新后的HTML模板代码
<ng-container *ngFor="let field of fieldsOfFormSearch" matColumnDef="{{field.urlSearch}}" > <th mat-header-cell *matHeaderCellDef> <span *ngIf="!field.status; else displayField"></span> <ng-template #displayField> <!-- Filtering here--> <mat-form-field appearance="fill"> <input matInput [formControl]="field.formControl" (input)="applyFilters()" > </mat-form-field> </ng-template> </th> </ng-container>
报错原因
你在模板里绑定的[formControl]="field.formControl"是个字符串(比如column.columnDef + 'Search'),但formControl指令要求接收FormControl实例,不是字符串,这就导致Angular无法识别控件,抛出错误。
修复方案
1. 修正TS代码:给field绑定FormControl实例
修改fieldsOfFormSearch的初始化和控件创建逻辑,直接把FormControl实例赋值给field.formControl:
// => Form element that will be displayed in second row this.fieldsOfFormSearch = this.tableColumns.map((column) => ({ status: column.isFilterable !== undefined, formControl: null, // 先占位,后续赋值实例 urlSearch: this.model + `[${column.columnDef}]`, })); // => Create form controls for filter inputs and add them to the FormGroup this.fieldsOfFormSearch .filter((field) => field.status) .forEach((field) => { let formControl = new FormControl(); field.formControl = formControl; // 把实例绑定到field的formControl属性 this.formGroup.addControl(field.urlSearch, formControl); formControl.valueChanges .pipe(debounceTime(300), distinctUntilChanged()) .subscribe((value: any) => { this.filterValues[field.urlSearch] = value; this.applyFilters(); }); });
2. 修正模板:移除冗余事件,绑定正确的控件
已经通过valueChanges监听值变化,不需要再绑定(input)事件,模板修改为:
<ng-container *ngFor="let field of fieldsOfFormSearch" matColumnDef="{{field.urlSearch}}" > <th mat-header-cell *matHeaderCellDef> <span *ngIf="!field.status; else displayField"></span> <ng-template #displayField> <mat-form-field appearance="fill"> <input matInput [formControl]="field.formControl" > </mat-form-field> </ng-template> </th> </ng-container>
3. 可选优化:用FormBuilder简化FormGroup创建
注入FormBuilder可以让代码更简洁,在组件里做如下修改:
import { FormBuilder, FormGroup, FormControl } from '@angular/forms'; // 组件构造函数 constructor(private fb: FormBuilder) { this.formGroup = this.fb.group({}); }
额外优化建议
- 记得在组件销毁时取消订阅,避免内存泄漏:
import { Subscription } from 'rxjs'; import { OnInit, AfterViewInit, OnDestroy } from '@angular/core'; // 实现OnDestroy接口 export class GridViewComponent implements OnInit, AfterViewInit, OnDestroy { private subscriptions = new Subscription(); // 在创建订阅时添加到集合 this.subscriptions.add( formControl.valueChanges .pipe(debounceTime(300), distinctUntilChanged()) .subscribe((value: any) => { this.filterValues[field.urlSearch] = value; this.applyFilters(); }) ); // 组件销毁时取消所有订阅 ngOnDestroy(): void { this.subscriptions.unsubscribe(); } } - 确保
tableColumns中的isFilterable属性设置正确,避免创建不必要的过滤控件。
内容的提问来源于stack exchange,提问作者Fadly Dzil
相关产品推荐
相关产品推荐

