[(ngModel)]和formControlName区别

148 阅读2分钟
  • 场景:在modal弹窗中 当在变更检测周期(change detection cycle)中修改了绑定的属性,Angular 会执行一次新的变更检测过程来确保所有绑定的值都是最新的。而如果在同一个变更检测周期中,绑定的值又发生了改变,就会触发 ExpressionChangedAfterItHasBeenCheckedError 错误

  • 原因:这个错误通常发生在组件初始化阶段,当组件的初始状态和模板中的数据绑定发生冲突时。在你的情况下,可能是因为你在 NzModalService 的回调中去修改了绑定的属性,而这个回调是在组件初始化完成后被调用的。

  • 解决方法:解决这个问题有几种方法:

      1. 使用 setTimeout 延迟执行代码:将可能引发错误的代码包装在一个 setTimeout 中,将其推迟到下一个变更检测周期执行。例如:
    constructor(private modalService: NzModalService) {}
    ngOnInit() {
      setTimeout(() => {
        // 可能引发错误的代码
      });
    }
    
      1. 使用 NgZone 运行代码:将可能引发错误的代码包装在 NgZone.run() 方法中,以确保它在 Angular 的变更检测周期之外执行。例如:
    constructor(private modalService: NzModalService, private ngZone: NgZone) {}
    ngOnInit() {
      this.ngZone.runOutsideAngular(() => {
        // 可能引发错误的代码
      });
    }
    
      1. 使用 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 });
  }