将行从Mat表传递到Mat对话框以填充反应形式时,Angular 8表达式更改错误

时间:2019-07-19 18:17:11

标签: angular angular-reactive-forms lifecycle

我知道之前曾有人问过这个问题,但是我尝试了几个答案,但没有任何效果。我正在与臭名昭著的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'.

说明:

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

(click)="onEditItem(row)"

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

我能够确定该错误是由直接从模板传递该行引起的。 当未传递行或对值进行硬编码时,它可以正常工作而不会出现错误。

例如,“添加”按钮onAddRow会打开完全相同的对话框,并且不会出现错误。 “添加”按钮显示在表标题处,并且不会从模板中连续传递,而只是初始化一个空对象并将其传递到对话框数据中。另外,如果只是在onEditRow函数中对值进行了硬编码(不从模板传递行),则对话框将加载并填充表单,而不会出现错误。

尝试修复:

我尝试使用许多技术,包括强制进行更改检测,将逻辑移至AfterView init,该网站herehere上的许多其他答案以及博客文章{{3 }}和here等。不幸的是,我仍然在努力解决这个问题,并且对为什么在对数据进行硬编码而不是从模板传递数据时为什么会起作用感到困惑。

代码

表组件

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:

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

尝试克隆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属性将其一一替换来对其进行了测试。在我到达未定义的属性之前,它会起作用。例如,如果“描述”未定义,则会引发错误。为解决此问题,在将其传递到对话框并填充表单控件之前,我确保已设置所有项目属性。

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

更新3:

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

此外,克隆item属性似乎也无法解决问题。

更新4:

该问题的另一个可能原因是在模板中,我在表单控件上以“模板驱动”方式required="true"设置了必填属性,但是忘记也以“反应式”方式将其设置为必填{ {1}}。我在模板中设置了必填属性,因为我遇到了here。我在TS组件中添加了所需的验证器,该错误似乎不再出现。但是,我并不完全相信它已解决,因为在将其恢复为以前的状态后,我无法再次进行复制。我怀疑这是一种间歇性问题,可能会在以后出现。

1 个答案:

答案 0 :(得分:1)

建议的解决方案

我的猜测是,可以通过将<authentication mode="Windows" />参数传递给item后再克隆它来解决此问题。

关于根问题的理论

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

位先进的情况下,因此最容易测试以确保我会说。