NG0100错误求助:ExpressionChangedAfterItHasBeenCheckedError排查
解决Angular表格列隐藏功能引发的NG0100错误
问题根源
NG0100错误(ExpressionChangedAfterItHasBeenCheckedError)是因为你在Angular变更检测完成后的ngAfterViewChecked钩子中修改了displayedColumns的值,导致Angular检测到表达式在检查后发生了变化。当前在ngAfterViewChecked里调用getDisplayedColumns()的操作直接触发了这个问题。
解决方案
1. 删除ngAfterViewChecked钩子
直接移除这个钩子方法,避免在变更检测完成后修改组件状态:
// 删掉这个方法 // ngAfterViewChecked(){ // this.getDisplayedColumns(); // }
2. 优化列定义的初始化逻辑
不要在类属性初始化阶段直接依赖FormControl的value(此时组件还未完成初始化,this.name!.value可能不是预期值),改为在ngOnInit中初始化列定义:
ngOnInit(): void { // 初始化列定义,设置初始隐藏状态 this.columnDefinitions = [ { def: 'name', label: 'name dom', hide: !this.name!.value }, // 其他列定义... ]; // 初始化显示列 this.getDisplayedColumns(); this.domSerice.getAllDom().subscribe(x =>{ this.dataSource = new MatTableDataSource<Dom>(x); this.dataSource.filterPredicate = this.customFilterPredicate(); }) }
3. 在列隐藏开关的valueChanges中同步更新显示列
在ngAfterViewInit的merge订阅里,更新columnDefinitions后立即调用getDisplayedColumns(),确保变更发生在变更检测周期内:
ngAfterViewInit() { let o1:Observable<boolean> = this.name!.valueChanges; // ...其他Observable定义 merge(o1,o2,o3,o4,o5,o6,o7,o8).subscribe( { next: (v)=>{ // 更新对应列的隐藏状态 this.columnDefinitions[0].hide = !this.name!.value; // ...其他列的hide更新逻辑 // 立即更新显示列 this.getDisplayedColumns(); } }); // 在这里设置分页和排序,确保ViewChild实例已加载 this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; // ...其他筛选逻辑 }
4. 可选:手动触发变更检测(如果仍有问题)
如果偶尔还是出现变更检测异常,可以注入ChangeDetectorRef手动标记变更:
// 构造函数中注入 constructor( private _httpClient: HttpClient, private _liveAnnouncer: LiveAnnouncer, private cdr: ChangeDetectorRef // 新增 ) { this.domSerice = new DomService(this._httpClient); } // 在valueChanges订阅中调用 merge(o1,o2,o3,o4,o5,o6,o7,o8).subscribe( { next: (v)=>{ this.columnDefinitions[0].hide = !this.name!.value; // ...其他列更新 this.getDisplayedColumns(); this.cdr.detectChanges(); // 手动触发变更检测 } });
5. 修复分页和排序的设置时机
原代码在ngOnInit中设置this.dataSource.paginator = this.paginator时,paginator还未被@ViewChild获取到(ViewChild在ngAfterViewInit阶段才会赋值),所以必须把这部分逻辑移到ngAfterViewInit中,确保实例已存在。
修改后的核心代码片段
export class DomTableComponent implements OnInit, AfterViewInit { filterValues: any = {name:'',...}; filterSelectObj: any[] = []; form:FormGroup = new FormGroup({ name: new FormControl(true), ... }); name = this.form.get('name'); ... // 先声明列定义数组,后初始化 columnDefinitions: Array<{def: string, label: string, hide: boolean}> = []; nameFilter = new FormControl(); ... globalFilter = ''; displayedColumns: string[] = []; empFilters: EmpFilter[]=[]; domSerice: DomService; dataSource= new MatTableDataSource<Dom>([]); @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; constructor( private _httpClient: HttpClient, private _liveAnnouncer: LiveAnnouncer, private cdr: ChangeDetectorRef // 新增 ) { this.domSerice = new DomService(this._httpClient); } ngOnInit(): void { // 初始化列定义 this.columnDefinitions = [ { def: 'name', label: 'name dom', hide: !this.name!.value}, ... ]; // 初始化显示列 this.getDisplayedColumns(); this.domSerice.getAllDom().subscribe(x =>{ this.dataSource = new MatTableDataSource<Dom>(x); this.dataSource.filterPredicate = this.customFilterPredicate(); }) } ngAfterViewInit() { let o1:Observable<boolean> = this.name!.valueChanges; ... merge(o1,o2,o3,o4,o5,o6,o7,o8).subscribe( { next: (v)=>{ this.columnDefinitions[0].hide = !this.name!.value; ... this.getDisplayedColumns(); this.cdr.detectChanges(); // 可选:手动触发变更检测 } }); // 正确设置分页和排序 this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; this.nameFilter.valueChanges.subscribe((filterValue) => { this.filterValues['name'] = filterValue; this.dataSource.filter = JSON.stringify(this.filterValues); }); ... } // ...其他方法保持不变 getDisplayedColumns() { this.displayedColumns = this.columnDefinitions.filter(cd=>!cd.hide).map(cd=>cd.def); } }
修复逻辑说明
- 移除
ngAfterViewChecked中的状态修改,避免在变更检测完成后触发值变更。 - 将列定义的初始化移到
ngOnInit,确保依赖的FormControl已完成初始化。 - 在列开关的
valueChanges订阅中直接更新显示列,确保变更发生在Angular的变更检测周期内。 - 修正
paginator和sort的设置时机,确保实例已被正确获取。
内容的提问来源于stack exchange,提问作者Jack Boch
相关产品推荐
相关产品推荐

