Search code examples
angulartypescriptprimeng

editable input number gets saved and pushed when i click cancel or even without clicking on save


I have a datatable, chart, and Latest value added label.

  • Table and chart are displaying time-series data that contains: the value time for the last 30 minutes and its numerical random value from 0 to 999.
  • Every 10 seconds new object gets pushed into the array and the oldest one is deleted so only the last 30 minutes are showing all the time. Latestvalue label is displaying the latest value added.

I have issue with editing the rows. So when i click edit and type something in the input (only numbers) and then click cancel edit (X) the values gonna get changed in the table and chart after the 10 seconds. Which it shouldnt since i cancel the edit. Same thing when i type in the input and just wait without saving the edit or canceling, the values gonna get changed.

I think it’s something with the two way binding in the table but not sure how to fix

I tried doing this but same result:

clonedData = [];

ngOnChanges() {
this.clonedData = [...tableData]}

and then in html table for the value i passed clonedData instead of tableData

here is stackblitz link : https://stackblitz.com/edit/angular-43vasb?file=src%2Fapp%2Fmain%2Ftable%2Ftable.component.ts,src%2Fapp%2Fmain%2Fmain.component.ts,src%2Fapp%2Finterface.ts,src%2Fapp%2Fdata.service.ts,src%2Fapp%2Fmain%2Ftable%2Ftable.component.html

table ts

export class TableComponent {
  @Input() tableData: IData[] = [];

  editedData: { [s: string]: IData } = {};

  constructor(private dataService: DataService) {}

  onRowEditInit(data: IData) {
    this.editedData[data.id] = { ...data };
    console.log(this.editedData[data.id]);
  }

  editRow(data: IData, row: any) {
    this.tableData[row] = data;
    this.dataService.setData(this.tableData);
    delete this.editedData[data.id];
  }

  onRowEditCancel(data: IData, row: number) {
    this.tableData[row] = this.editedData[data.id];
    delete this.editedData[data.id];
  }
}

table html

<div class="container">
  <p-table
    [value]="tableData"
    dataKey="id"
    editMode="row"
    scrollHeight="flex"
    styleClass="p-datatable-gridlines"
    [rows]="8"
    [scrollable]="true"
  >
    <ng-template pTemplate="header">
      <tr>
        <th class="time">Time</th>
        <th class="value">Value</th>
        <th class="action"></th>
      </tr>
    </ng-template>
    <ng-template
      pTemplate="body"
      let-data
      let-editing="editing"
      let-ri="rowIndex"
    >
      <tr [pEditableRow]="data">
        <td class="p-column-title">
          <p-cellEditor>
            <ng-template pTemplate="input">
              <input pInputText type="text" [(ngModel)]="data.time" />
            </ng-template>
            <ng-template pTemplate="output">
              {{ data.time | date : "yyyy-MM-dd HH:mm:ss" }}
            </ng-template>
          </p-cellEditor>
        </td>
        <td>
          <p-cellEditor>
            <ng-template pTemplate="input">
              <input pInputText type="number" [(ngModel)]="data.value" />
            </ng-template>
            <ng-template pTemplate="output">
              {{ data.value | number : "1.2-2" }}
            </ng-template>
          </p-cellEditor>
        </td>
        <td class="action-button">
          <div class="flex align-items-center justify-content-center gap-2">
            <button
              *ngIf="!editing"
              pButton
              pRipple
              type="button"
              (click)="onRowEditInit(data)"
              pInitEditableRow
              icon="pi pi-pencil"
              class="p-button-rounded p-button-text"
            ></button>
            <button
              *ngIf="editing"
              pButton
              pRipple
              (click)="editRow(data, ri)"
              type="button"
              pSaveEditableRow
              icon="pi pi-check"
              class="p-button-rounded p-button-text p-button-success mr-2"
            ></button>
            <button
              *ngIf="editing"
              pButton
              pRipple
              (click)="onRowEditCancel(data, ri)"
              type="button"
              pCancelEditableRow
              icon="pi pi-times"
              class="p-button-rounded p-button-text p-button-danger"
            ></button>
          </div>
        </td>
      </tr>
    </ng-template>
  </p-table>
</div>

Solution

  • [Note: most examples for the PrimeNG are not paired with live chart, so they don't actually cover your use-case, and that's why you're having issues if you're following those tutorials.]

    Yes, the problem is in binding: you're binding the time/value to an actual table data, so whenever you edit it - regardless of accepting or cancelling changes - you're effectively changing the original table data. So, you should bind to other variables. I've edited your stackblitz code with these changes:

    1. Introduced [table.comp.ts] three variables idBeingEdited, timeBeingEdited and valueBeingEditedand bound them in template [table.comp.html] instad of data.time and data.value. That way we're keeping the 'original' data (particularly important for the order of rows) intact and working only with these local/bound values.
    2. Changed your onRowEditInit() method to set the values of these variables to the actual data being edited.
    3. Removed everything from onRowEditCancel() method, because now we're working with non-original data and no changes (in logic or data) will need to be made if you just cancel the operation.
    4. Changed editRow() method - removed parameters (because we don't need template data here: we have local bounded values) - to change only the original data at the required id, so that nothing else changed.

    So, changes in table.com.ts:

    idBeingEdited: number = null;
    timeBeingEdited: number = null;
    valueBeingEdited: number = null;
    
    (...)
    
    onRowEditInit(data: IData) {
        this.idBeingEdited = data.id;
        this.timeBeingEdited = data.time;
        this.valueBeingEdited = data.value;
    }
    
    editRow() {
        this.tableData.forEach((row, i) => {
          if (row.id == this.idBeingEdited) {
            this.tableData[i].time = this.timeBeingEdited;
            this.tableData[i].value = this.valueBeingEdited;
          }
        });
        this.dataService.setData(this.tableData);
    }
    

    and in table.comp.html:

    • row 28 from <input pInputText type="text" [(ngModel)]="data.time" /> becomes <input pInputText type="text" [(ngModel)]="timeBeingEdited" />
    • row 38 from <input pInputText type="number" [(ngModel)]="data.value" /> becomes <input pInputText type="number" [(ngModel)]="valueBeingEdited" />
    • row 61 from (click)="editRow(data, ri)" becomes (click)="editRow()"

    Here's a stackblitz demo.