ng2-smart-table icon indicating copy to clipboard operation
ng2-smart-table copied to clipboard

Error when editing a same row twice

Open pedrolvr opened this issue 8 years ago • 9 comments

I am using ServerDataSource:

  let data = event.newData;

  this.tableService.update(data)
     .then((result) => {
         event.confirm.resolve(result[0]);
      }).catch((err) => {
         event.confirm.reject(err);
      });

Error: Uncaught (in promise): Error: Element was not found in the dataset Error: Element was not found in the dataset at ServerDataSource.LocalDataSource.find (http://localhost:8080/vendor.bundle.js:842:31) [angular] at LocalDataSource.update (http://localhost:8080/vendor.bundle.js:831:19) [angular] at new ZoneAwarePromise (http://localhost:8080/polyfills.dll.js:4471:29) [angular] at ServerDataSource.LocalDataSource.update (http://localhost:8080/vendor.bundle.js:830:16) [angular] at http://localhost:8080/vendor.bundle.js:1578:26 [angular] at Object.onInvoke (http://localhost:8080/vendor.dll.js:30260:37) [angular] at Zone.run (http://localhost:8080/polyfills.dll.js:4033:43) [angular => angular] at http://localhost:8080/polyfills.dll.js:4455:57 [angular] at Object.onInvokeTask (http://localhost:8080/vendor.dll.js:30251:37) [angular] at ZoneDelegate.invokeTask (http://localhost:8080/polyfills.dll.js:4194:40) [angular] at Zone.runTask (http://localhost:8080/polyfills.dll.js:4071:47) [ => angular] at drainMicroTaskQueue (http://localhost:8080/polyfills.dll.js:4353:35) [] at XMLHttpRequest.ZoneTask.invoke (http://localhost:8080/polyfills.dll.js:4269:25) [] at resolvePromise (http://localhost:8080/polyfills.dll.js:4421:31) [angular] at resolvePromise (http://localhost:8080/polyfills.dll.js:4406:17) [angular] at http://localhost:8080/polyfills.dll.js:4455:17 [angular] at Object.onInvokeTask (http://localhost:8080/vendor.dll.js:30251:37) [angular] at ZoneDelegate.invokeTask (http://localhost:8080/polyfills.dll.js:4194:40) [angular] at Zone.runTask (http://localhost:8080/polyfills.dll.js:4071:47) [ => angular] at drainMicroTaskQueue (http://localhost:8080/polyfills.dll.js:4353:35) [] at XMLHttpRequest.ZoneTask.invoke (http://localhost:8080/polyfills.dll.js:4269:25) [] ------------- Elapsed: 14 ms; At: Fri Feb 24 2017 17:24:24 GMT-0300 (BRT) -------------
at getStacktraceWithUncaughtError (http://localhost:8080/polyfills.dll.js:3790:12) [angular] at new LongStackTrace (http://localhost:8080/polyfills.dll.js:3784:22) [angular] at Object.onScheduleTask (http://localhost:8080/polyfills.dll.js:3840:18) [angular]

pedrolvr avatar Feb 24 '17 20:02 pedrolvr

Did you solve this problem? Do you know the reason?

kirkor avatar Jun 13 '17 15:06 kirkor

I am seeing the same problem after I switched from a local data source to a server data source. The server data source is using paging and extends LocalDataSource.

The steps are:

  1. Click edit on a row
  2. Click update - the row is updated on the server
  3. Click edit on the same row again
  4. Click update - the row is updated on the server but I get the error shown above when calling event.confirm.resolve

Here is the onSaveConfirm method that gets called when clicking Update.

public onSaveConfirm(event: any): void {
    const item = event.newData;
    this.apiService.saveItem(item).subscribe(
        savedItem => {
            event.confirm.resolve(savedItem);
        }
    );
}

I thought maybe the problem is that I am passed a different (updated) object - savedItem - to resolve but I tried passing the original object - item - and the same problem happens.

My theory is that it has something to do with the page getting reloaded after the first edit and there is some mismatched data when the second edit happens. I see this order of events:

  1. First save occurs and event.confirm.resolve is called
  2. getElements on server data source is called to reload page 1 and it completes
  3. Edit is clicked and table moves to edit mode
  4. Update is clicked, save to server happens, event.confirm.resolve is called which causes exception.

Could item 2 (getElements) be causing some part of the model to be out of sync so during step 4 the "find" call cannot match the same element because they are different elements with the same data.

If this is the case a workaround may be to completely replace LocalDataSource instead of extending it. I will try that when I have time.

daddyman avatar Jun 28 '17 17:06 daddyman

The problem is that there is code that assumes the object instance will be the same before and after the update occurs.

In local.data-source.ts the find method compares the instances using ===.

find(element: any): Promise<any> {
    const found = this.data.find(el => el === element);
    if (found) {
      return Promise.resolve(found);
    }
   return Promise.reject(new Error('Element was not found in the dataset'));
}

In grid.ts the call to findRowByData compares using ===

this.source.onUpdated().subscribe((data) => {
    const changedRow = this.dataSet.findRowByData(data);
    changedRow.setData(data);  //fails in here because data is undefined
});

data-set.ts:

findRowByData(data: any): Row {
    return this.rows.find((row: Row) => row.getData() === data);
}

When using a server data source the elements change each time they are loaded. The first edit/update works because the object hasn't change but then getElements is called on the data source and the elements are different (in some places). When the edit/update occurs there is a mismatch.

To get around this I added update and find methods to completely override LocalDataSource to match elements by the id field I have in my objects. When the object is updated I copy the fields from the new values object passed to the update method and send that object to the updated event.

My first attempt was to not extend LocalDataSource but I still had the problem of the issue with data-set.ts comparing reference instead of testing by an id field.

Here is what I added to my data source class - which extends from LocalDataSource. I can now edit/update multiple times.

public update(element: Trigger, values: Trigger): Promise<any> {
    return new Promise((resolve, reject) => {
        this.find(element).then(found => {
            //Copy the new values into element so we use the same instance
            //in the update call.
            element.name = values.name;
            element.enabled = values.enabled;
            element.condition = values.condition;

            //Don't call super because that will cause problems - instead copy what DataSource.ts does.
            ///super.update(found, values).then(resolve).catch(reject);
            this.emitOnUpdated(element);
            this.emitOnChanged('update');
            resolve();
        }).catch(reject);
    });
}
public find(element: Trigger): Promise<Trigger> {
    //Match by the trigger id
    const found: Trigger = this.data.find(el => el.id === element.id);
     if (found) {
        return Promise.resolve(found);
    }
    return Promise.reject(new Error('Element was not found in the dataset'));
}

daddyman avatar Jun 28 '17 21:06 daddyman

Works for me! Thx!

kirkor avatar Jun 29 '17 19:06 kirkor

@daddyman Thank you , Your solution works for me, But this bug should be solved without this work around .

almgwary avatar May 20 '18 14:05 almgwary

I resolve that by changing :

  1. LocalDataSource.prototype.find in local-data-dource.js var found = this.data.find(function (el) { return el.id === element.id; });

  2. DataSet.prototype.findRowByData in data-set.js return this.rows.find(function (row) { return row.getData().id === data.id; });

FarhatW avatar May 10 '19 09:05 FarhatW

I solved by invoking the refresh() method of the datasource after event.confirm.resolve() in onEditConfirm function: e.g .: onEditConfirm(ev){ this.httpapi.edit(ev.newData).subscribe( result => { if (result.status) { ... ev.confirm.resolve(result.data); this.source.refresh(); } else ...

this works fine for me.

blushift80 avatar Oct 10 '19 17:10 blushift80

thanks it is working for me

jigneshHchauhan avatar Jan 17 '20 12:01 jigneshHchauhan

If anyone makes his own fork. Using isEqual from lodash does solve the problem with less changes, too.

// start of file
import { isEqual } from 'lodash';
...
// in local.data-source.ts
remove(element: any): Promise<any> {
  this.data = this.data.filter(el => !isEqual(el, element));
  return super.remove(element);
}

find(element: any): Promise<any> {
    const found = this.data.find(el => isEqual(el, element));
    if (found) {
      return Promise.resolve(found);
    }
   return Promise.reject(new Error('Element was not found in the dataset'));
}

// in data-set.ts
findRowByData(data: any): Row {
  return this.rows.find((row: Row) => isEqual(row.getData(), data));
}

Shadowsith avatar Jun 24 '21 22:06 Shadowsith