Reputation: 209
Plunker available here : http://plnkr.co/edit/vczMKlnY5yxXtzrh955m?p=preview
The use case looks pretty simple :
On the save click in the popup, I want to update the entity. On popup close I want to unselect the row.
By setting the selection to null in the updateSuccess event, the error on save disappears (TRICK 1 in the plunker).
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).
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 :
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 :
<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
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
<!-- 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>
Upvotes: 3
Views: 4161
Reputation: 6655
To get rid of ExpressionChangedAfterItHasBeenCheckedError
you have to manually trigger change detection in your app.component (see Expression ___ has changed after it was checked)
So your onClose
method becomes :
public onClose() {
this.selectedEntity = {};
this.cdr.detectChanges();
}
See Plunker
Upvotes: 3