-
场景:在modal弹窗中 当在变更检测周期(change detection cycle)中修改了绑定的属性,Angular 会执行一次新的变更检测过程来确保所有绑定的值都是最新的。而如果在同一个变更检测周期中,绑定的值又发生了改变,就会触发
ExpressionChangedAfterItHasBeenCheckedError错误 -
原因:这个错误通常发生在组件初始化阶段,当组件的初始状态和模板中的数据绑定发生冲突时。在你的情况下,可能是因为你在
NzModalService的回调中去修改了绑定的属性,而这个回调是在组件初始化完成后被调用的。 -
解决方法:解决这个问题有几种方法:
-
- 使用
setTimeout延迟执行代码:将可能引发错误的代码包装在一个setTimeout中,将其推迟到下一个变更检测周期执行。例如:
- 使用
constructor(private modalService: NzModalService) {} ngOnInit() { setTimeout(() => { // 可能引发错误的代码 }); }-
- 使用
NgZone运行代码:将可能引发错误的代码包装在NgZone.run()方法中,以确保它在 Angular 的变更检测周期之外执行。例如:
- 使用
constructor(private modalService: NzModalService, private ngZone: NgZone) {} ngOnInit() { this.ngZone.runOutsideAngular(() => { // 可能引发错误的代码 }); }-
- 使用
ChangeDetectorRef手动触发变更检测:在代码执行完毕后,使用ChangeDetectorRef的detectChanges()方法手动触发变更检测。例如:
- 使用
import { ChangeDetectorRef } from '@angular/core'; constructor( private modalService: NzModalService, private cdr: ChangeDetectorRef ) {} ngOnInit() { // 可能引发错误的代码 this.cdr.detectChanges(); }- 场景:同时使用容易产生bug,两个机制是有所不同的,尤其是第三方的封装的表单,未必会同时生效。有时需要手动去更ngModel的值。
-
<nz-table
#nzTable
[hidden]="auditLists.value?.length === 0"
nzSize="small"
nzShowPagination="false"
[nzData]="auditLists.value"
[nzFrontPagination]="false"
[nzScroll]="{ x: '200px' }"
>
<thead>
<tr>
<th nzWidth="130px">系统</th>
<th>名字1</th>
<th *ngIf="containProdEnv">名字2</th>
<th *ngIf="containProdEnv">名字3</th>
</tr>
</thead>
<tbody>
<ng-container formArrayName="auditList">
<tr
*ngFor="let item of auditLists.controls; let i = index"
[formGroupName]="i"
>
<td>
{{ item.value?.svcDefAppName }}
</td>
<td>
<nz-checkbox-group
[(ngModel)]="item.value.providerDevUsers"
[ngModelOptions]="{ standalone: true }"
/>
<nz-select
*ngIf="isShowOtherSearch(item.value.providerDevUsers)"
[(ngModel)]="item.value.otherProviderUsers"
nzPlaceHolder="请选择授权用户"
nzShowSearch
nzAllowClear
nzMode="multiple"
[ngModelOptions]="{ standalone: true }"
(ngModelChange)="searchUserDataChange($event)"
(nzOnSearch)="
searchUserData($event, i, 'providerDevOptions')
"
[nzServerSearch]="true"
>
<nz-option
*ngFor="let option of item.value.providerDevOptions"
nzCustomContent
[nzLabel]="option.name"
[nzValue]="option"
[nzDisabled]="option.disabled"
>
{{ option.name }}({{ option.department }})
<span *ngIf="option.disabled">已勾选审批人</span>
</nz-option>
</nz-select>
</td>
<td *ngIf="containProdEnv">
<nz-checkbox-group
[(ngModel)]="item.value.providerOperUsers"
[ngModelOptions]="{ standalone: true }"
/>
<nz-select
*ngIf="isShowOtherSearch(item.value.providerOperUsers)"
[(ngModel)]="item.value.otherProviderOperUsers"
nzPlaceHolder="请选择授权用户"
nzShowSearch
nzAllowClear
nzMode="multiple"
[ngModelOptions]="{ standalone: true }"
(nzOnSearch)="
searchUserData($event, i, 'providerOperOptions')
"
[nzServerSearch]="true"
>
<nz-option
*ngFor="let option of item.value.providerOperOptions"
nzCustomContent
[nzLabel]="option.name"
[nzValue]="option"
[nzDisabled]="option.disabled"
>
{{ option.name }}({{ option.department }})
<span *ngIf="option.disabled">已勾选审批人</span>
</nz-option>
</nz-select>
</td>
<td *ngIf="containProdEnv">
<nz-checkbox-group
[(ngModel)]="item.value.calledOperUsers"
[ngModelOptions]="{ standalone: true }"
/>
<nz-select
*ngIf="isShowOtherSearch(item.value.calledOperUsers)"
[(ngModel)]="item.value.otherCalledOperUsers"
nzPlaceHolder="请选择授权用户"
nzShowSearch
nzAllowClear
nzMode="multiple"
[ngModelOptions]="{ standalone: true }"
(nzOnSearch)="
searchUserData($event, i, 'calledOperOptions')
"
[nzServerSearch]="true"
>
<nz-option
*ngFor="let option of item.value.calledOperOptions"
nzCustomContent
[nzLabel]="option.name"
[nzValue]="option"
[nzDisabled]="option.disabled"
>
{{ option.name }}({{ option.department }})
<span *ngIf="option.disabled">已勾选审批人</span>
</nz-option>
</nz-select>
</td>
</tr>
</ng-container>
</tbody>
</nz-table>
// ngModelChange 保存当前选择
onUserSelectionChange(selectedUsers: any[], index: number, field: string) {
// 保存当前选择的值
this.userSelections.set(`${index}-${field}`, selectedUsers);
}
searchUserData(searchText: string, index: number, optionType: string) {
// 需要使用onUserSelectionChange手动赋值。不然使用search的会导致其他的i的select值被清除掉
const control = this.auditLists.at(index);
const options = this.fetchOptions(searchText);
control.patchValue({ [optionType]: options }, { emitEvent: false });
}