2

我知道以前有人问过这个问题,但我尝试了几个答案,但没有任何效果。我正在与臭名昭著的ExpressionChangedAfterItHasBeenCheckedError. 该代码正在运行,但错误消息出现在控制台中。

错误:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'disabled: false'. Current value: 'disabled: true'.

删除[disabled]="itemForm.invalid"条件后,会出现此新错误。

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ng-valid: true'. Current value: 'ng-valid: false'.

描述:

我有一个显示多行数据的材料表。每行旁边显示一个“编辑”按钮。该行作为单击事件的参数传递给 onRowEdit 函数。

(click)="onEditItem(row)"

onEditItem 函数打开一个 Mat Dialog 组件,并将模板中的行作为 Dialog Data 传入。然后使用 Dialog 数据填充 FormGroup 以显示表单内的值。

我能够确定错误是由直接从模板传入行引起的。当一行没有传入,或者值被硬编码时,它可以正常工作而没有错误。

例如,“添加”按钮onAddRow会打开完全相同的对话框,并且可以正常工作。Add 按钮显示在表头,不会从模板中传入一行,只是初始化一个空对象并将其传递到对话框数据中。此外,如果这些值只是在onEditRow函数中硬编码(不从模板传递行),则对话框加载并填充表单而没有错误。

尝试修复:

我尝试使用许多技术,包括强制更改检测、将逻辑移动到 AfterView init、此站点上的许多其他答案以及此处此处博客文章中提供的各种建议等等。不幸的是,我仍然在努力解决这个问题,并且对为什么当数据被硬编码时它起作用而感到困惑,但当它从模板传入时却不起作用。

代码

表组件

TS

  // ERROR WHEN A ROW IS PASSED IN FROM THE TEMPLATE DIRECTLY
  public onEditItem(item): void {
    const dialogData = {
      editMode: true,
      selectedItem: item
      // WORKS WITHOUT THE ERROR IF ITEM VALUES ARE HARD-CODED INTO THE DIALOG DATA VARIABLE
      // selectedItem: { Title: 'Example', Description: 'Example description' }
    };

    this.openItemDetailDialog(dialogData);
  }

  // WORKS
  public onAddItem(): void {
    const dialogData = {
      editMode: false,
      selectedItem: {}
    }
    this.openItemDetailDialog(dialogData);
  }

  private openItemDetailDialog(dialogData): void {
    const dialogRef = this.dialog.open(ItemDetailComponent, {
      height: '90%',
      width: '90%',
      data: dialogData,
      disableClose: true,
    });
  }

HTML

<table mat-table [dataSource]="itemsDataSource">
  <ng-container [matColumnDef]="column.name" *ngFor="let column of initCols;">
    <th mat-header-cell *matHeaderCellDef>
      {{ column.display }}
    </th>
    <td mat-cell *matCellDef="let element">
      <ng-container>
        {{ element[column.name] }}
      </ng-container>
    </td>
  </ng-container>
  <!-- Table actions -->
  <ng-container matColumnDef="actions">
    <th mat-header-cell *matHeaderCellDef>
      <div mat-header>
        <button type="button" mat-button
          (click)="onAddItem()">
          <mat-icon>add_circle</mat-icon>
        </button>
      </div>
    </th>
    <td mat-cell class="xs" *matCellDef="let row">
      <ng-container>
        <button type="button" mat-button
          (click)="onEditItem(row)">
          <mat-icon>edit</mat-icon>
        </button>
      </ng-container>
    </td>
  </ng-container>
  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;" [ngClass]="uiService.getClassUnsaved(deletedItems, row)"></tr>
</table>

对话框组件

TS

ngOnInit() {

  // Get currently Selected Item to populate Item Form values
  this.item = this.dialogData.selectedItem;

  // Initialize Item values
  const itemValues = this.itemService.initItemValues(this.item);

  // Initialize Item Form
  this.itemService.initItemForm(itemValues);

  // Get current value of Item Form
  this.itemFormSub = this.itemService.itemForm$
    .subscribe(itemForm => {
      // Set local class member variable to populate the FormGroup in the template
      this.itemForm = itemForm;
      this.isLoadingResults = false;
    });

}

HTML

<h1 mat-dialog-title>Item Detail</h1>
<!-- Form -->
<form [formGroup]="itemForm" (ngSubmit)="onSubmit()">
  <div mat-dialog-content>
  <mat-form-field appearance="outline" floatLabel="always">
    <mat-label>Call Number</mat-label>
    <input matInput formControlName="Title" required="true">
  </mat-form-field>
  <!-- Actions --->
  <div mat-dialog-actions>
    <button type="submit" mat-raised-button color="primary"
      [disabled]="itemForm.invalid">
      Save Item
    </button>
  </div>
</form>

服务

// Create a BehaviorSubject to track and share updates to Record Form value
private itemForm: FormGroup;
itemFormSubject: BehaviorSubject<FormGroup> = new BehaviorSubject(this.itemForm);
itemForm$ = this.itemFormSubject.asObservable();

// Initialize Item values
public initItemValues(item: Item | any): Item {

  const itemValues: Item = {
    Title: item && item.Title ? item.Title : null,
    Description: item && item.Description ? item.Description : null,
  };

  return itemValues;
}

// Initialize a new Item Form and pre-populate values
public initItemForm(item: Item): void {

  // Initialize a new Item Form Group
  const itemForm = this.fb.group({
    Title: [item.Title, Validators.compose([
      Validators.minLength(3),
      Validators.maxLength(10),
      Validators.pattern(/^[A-Za-z0-9]*$/)
    ])],
    Description: [item.Description, Validators.compose([
      Validators.minLength(3),
      Validators.maxLength(100),
      Validators.pattern(/^[A-Za-z0-9]*$/)
    ])]
  });

  // Update current value of Item Form Subject to share with subscribers
  this.itemFormSubject.next(itemForm);

}

更新 1:

正如一位乐于助人的人所建议的那样,我尝试在将 item 参数传递到对话框之前对其进行克隆。不幸的是,这没有奏效。

尝试克隆 1:

  public onEditItem(item): void {
    const selectedItem = this.uiService.deepCopy(item);
    const dialogData = {
      editMode: true,
      selectedItem: selectedItem
    };

    this.openItemDetailDialog(dialogData);
  }

尝试克隆 2:

  public onEditItem(item): void {
    const selectedItem = this.uiService.deepCopy(item);
    const dialogData = {
      editMode: true,
      selectedItem: {
        Title: item.Title,
        Description: item.Description
      }
    };

    this.openItemDetailDialog(dialogData);
  }

更新 2:

我认为这可能是由于未定义 Item 属性之一引起的问题。我通过对值进行硬编码并用 Item 属性一一替换它们来测试它。它一直有效,直到我得到一个未定义的属性。例如,如果 Description 未定义,则会引发错误。为了解决这个问题,我确保在将所有项目属性传递到对话框并填充表单控件之前设置它。

      selectedItem: {
        Title: item.Title,
        Description: item.Description // Might be caused by this property being undefined
      }

更新 3:

该问题肯定是由需要但未填充初始值的表单控件引起的。在上面的更新 2 中,Description 是必需的,但 item.Description 未定义。因此,如果我手动设置 Description 的值,它会起作用。但是,如果在表单中使用空值初始化任何必需的属性,则它不起作用。我以为我可以通过设置值来解决这个问题,但事实证明这仍然是一个问题,因为在某些情况下,该项目没有加载表单时所需的属性。

此外,克隆 item 属性似乎并不能解决问题。

更新 4:

问题的另一个可能原因是在模板中,我required="true"在表单控件上以“模板驱动”方式设置了 required 属性,但忘记以“反应式”方式将其设置为 required Validators.required。我在模板中设置了 required 属性,因为我遇到了关于缺少 asterisk 的问题。我在 TS 组件中添加了所需的验证器,错误似乎不再出现。但是,我并不完全相信它已解决,因为在将其设置回以前的方式后我无法再次重现它。我怀疑这是某种间歇性问题,可能会在以后出现。

4

1 回答 1

1

建议的解决方案

我的猜测是,您可以通过在将item参数传递到onEditItem.

根本问题理论

我认为这个问题是由于item接受的参数onEditItem是在 mat 表使用的数据源中使用的引用。我认为它会引发错误,因为您正在更改表正在使用的对象。

有点高级的情况,所以最容易测试以确保我会说。

于 2019-07-19T18:30:36.820 回答