简体   繁体   English

PrimeNG / p-table / p-dialog / onHide()回调导致ExpressionChangedAfterItHasBeenCheckedError

[英]PrimeNG / p-table / p-dialog / onHide() callback causing ExpressionChangedAfterItHasBeenCheckedError

Plunker available here : http://plnkr.co/edit/vczMKlnY5yxXtzrh955m?p=preview 可在此处获得柱塞: http ://plnkr.co/edit/vczMKlnY5yxXtzrh955m?p=preview

The use case looks pretty simple : 用例看起来非常简单:

  • 1 component with a p-table of entities 1个组件,带有实体p表
  • 1 component with a p-dialog to update an entity 1个带有p对话框的组件,用于更新实体

On the save click in the popup, I want to update the entity. 在保存上,在弹出窗口中单击,我要更新实体。 On popup close I want to unselect the row. 在弹出窗口关闭时,我要取消选择该行。

  • When I close the popup (by clicking the cross) everything goes well. 当我关闭弹出窗口(通过单击叉号)时,一切顺利。
  • When I save the changes, I get the ExpressionChangedAfterItHasBeenCheckedError and the line remains selected. 保存更改后,出现ExpressionChangedAfterItHaHasBeenCheckedError,并且该行保持选中状态。
  • When I cancel the changes, I also get the ExpressionChangedAfterItHasBeenCheckedError and the line remains selected. 取消更改时,我还会收到ExpressionChangedAfterItHaHasBeenCheckedError,并且该行保持选中状态。

By setting the selection to null in the updateSuccess event, the error on save disappears (TRICK 1 in the plunker). 通过在updateSuccess事件中将选择设置为null,保存错误将消失(插销器中的TRICK 1)。

The only solution I managed to find to completely prevent the error is to disable the possibility to close the popup through the cross with [closable]="false" and never handle the display boolean in the popup but delegate this responsibility to the parent component (using the close event in the parent to set display to false) (TRICK 1 + TRICK 2 in the plunker). 我设法找到的完全避免错误的唯一解决方案是禁用使用[closable]="false"通过叉[closable]="false"跨弹出窗口的可能性,并且从不处理弹出窗口中的显示布尔值,而是将此责任委托给父组件(使用父项中的close事件将显示设置为false)(插塞中的TRICK 1 + TRICK 2)。

I can't figure a good solution without adding some tricks to force bindings to refresh manually. 如果不添加一些技巧来强制绑定手动刷新,就无法找到一个好的解决方案。

What would be the best solution here ? 最好的解决方案是什么?

app/app.component.ts : app / app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: 'app/app.template.html'
})
export class AppComponent {

  public display = false;

  public selectedEntity = null;

  public cols = [
    {field: 'id', header: 'ID'},
    {field: 'prop', header: 'Property'}
  ];

  public someEntities = [
    { id: 1, prop: 'foo' },
    { id: 2, prop: 'bar' }
  ];

  public onClick() {
    this.display = true;
  }

  public onRowSelect(event) {
    this.display = true;
  }

  public onClose() {
    this.selectedEntity = null;
    // XXX TRICK 2 : Uncomment to delegate the closing responsibility to this component
    //this.display = false;
  }

  public onUpdateSuccess() {
    // XXX TRICK 1 : Uncomment to prevent the error on save
    //this.selectedEntity = null;
    // XXX TRICK 2 : Uncomment to delegate the closing responsibility to this component
    //this.display = false;
  }
}

app/app.template.html : app / app.template.html:

<h2>PrimeNG Issue Template</h2>
<p>Please create a test case and attach the link of the plunkr to your github issue report.</p>

<p-table [columns]="cols" [value]="someEntities" (onRowSelect)="onRowSelect($event)" selectionMode="single" [(selection)]="selectedEntity">
  <ng-template pTemplate="header" let-columns>
    <tr>
      <th *ngFor="let col of columns">
          {{col.header}}
      </th>
    </tr>
  </ng-template>
  <ng-template pTemplate="body" let-rowData let-columns="columns">
    <tr [pSelectableRow]="rowData">
      <td *ngFor="let col of columns">
          {{rowData[col.field]}}
      </td>
    </tr>
  </ng-template>
</p-table>

<my-popup [(display)]="display" [myEntity]="selectedEntity" (close)="onClose()" (updateSuccess)="onUpdateSuccess($event)"></my-popup>

app/popup.component.ts 应用程序/ popup.component.ts

import { Component, Input, Output, OnChanges, EventEmitter } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';

@Component({
  selector: 'my-popup',
  templateUrl: 'app/popup.component.html'
})
export class MyPopupComponent implements OnChanges {
  private _display = false;
  @Input()
  get display() {
    return this._display;
  }

  set display(_display: boolean) {
    this._display = _display;
    this.displayChange.emit(_display);
  }

  @Input()
  public myEntity: any;

  @Output()
  private displayChange = new EventEmitter<boolean>();

  @Output()
  public close = new EventEmitter<void>();

  @Output()
  public updateSuccess = new EventEmitter<any>();

  public myForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.myForm = this.fb.group({
            myProp: ['', []]
        });
  }

  ngOnChanges() {
    if (this.display === true && this.myEntity) {
      this.myForm.reset({myProp: this.myEntity.prop});
    }
  }

  public onHide() {
    this.close.emit();
  }

  public onSubmit() {
    this.myEntity.prop = this.myForm.value.myProp;
    // Call to REST API to update the entity then emit the success
    this.updateSuccess.emit();
    // XXX TRICK 2 : Comment to delegate the closing responsibility to the parent component
    this.display = false;
  }
}

app/popup.component.html 应用程序/ popup.component.html

<!-- TRICK 2 : add [closable]=false to delegate the responsibility to the parent component -->
<!-- <p-dialog [(visible)]="display" (onHide)="onHide()" [modal]="true" [closable]="true"> -->
<p-dialog [(visible)]="display" (onHide)="onHide()" [modal]="true">
  <p-header>My gorgeous popup</p-header>
  <form id="myForm" [formGroup]="myForm" (ngSubmit)="onSubmit()">
    <input pInputText id="myProp" formControlName="myProp" />
  </form>
  <p-footer>
    <button pButton type="submit" label="Save" form="myForm"></button>
    <!-- TRICK 2 : replace "display = false" with "onHide()" to delegate the responsibility to the parent component -->
    <!-- <button pButton type="button" label="Cancel" (click)="onHide()"></button> -->
    <button pButton type="button" label="Cancel" (click)="display = false"></button>
  </p-footer>
</p-dialog>

To get rid of ExpressionChangedAfterItHasBeenCheckedError you have to manually trigger change detection in your app.component (see Expression ___ has changed after it was checked ) 要摆脱ExpressionChangedAfterItHasBeenCheckedError您必须在app.component中手动触发更改检测(请参阅Expression ___在检查后已更改

So your onClose method becomes : 因此,您的onClose方法变为:

public onClose() {
    this.selectedEntity = {};
    this.cdr.detectChanges();    
}

See Plunker 柱塞

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM