primefaces / primeng

The Most Complete Angular UI Component Library
https://primeng.org
Other
10.15k stars 4.54k forks source link

PrimeNG Angular Table "Converting circular structure to JSON" whenever table data contains non-trivial data #14428

Open rubmz opened 8 months ago

rubmz commented 8 months ago

Describe the bug

Our application uses template components in its table. These components are given a reference to their own AbstractControl for various reasons. The problem is, that as result these components are being stored in the selected property of the PrimeNG table when user selects a row. Then, when the user clicks on a row, Table.handleRowClick() is being called. Table.handleRowClick() calls Table.saveState() which tries to JSON.stringify() on Table.selected. JSON.stringify() doesn't like so much AbstractControls (and RXJS Subjects, which are also useful to pass to some table components) as they contain circular references internally. This raises an exception, and the state is not saved.

One could argue we should only store pure data in our table.data property, but it is many times important to pass more complex structures to the components. And with current implementation this breaks the handleRowClick() call.

A simple solution - pass a circular handler to JSON.stringify() as so:

const getCircularReplacer = () => {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

saveState() {
  ...
  storage.setItem(this.stateKey, JSON.stringify(state, getCircularReplacer()));
}

Environment

Angular 16.1.7 in browsers web site.

Reproducer

No response

Angular version

16.1.7

PrimeNG version

16.1.0

Build / Runtime

TypeScript

Language

TypeScript

Node version (for AoT issues node --version)

18.18.2

Browser(s)

Chrome 120.0.6099.129

Steps to reproduce the behavior

  1. Create a table component and pass rows [data] a some template components that accepts their own (or other) controls (any Angular control) as part of their row data.
  2. Click on a row data.

The result -> exception in the console log.

Expected behavior

The state is saved without exception.

mbraeuner commented 7 months ago

I had a similar issue with the tree table. I had also nested data inside the tree table component. Every time I selected a tree node primeng threw an exception screenshot primeng 2024-01-16 18-40-48

seems to be a recursive call of (https://github.com/primefaces/primeng/blob/239b86cdffe1ca4e8c0c03d13ad6b5ae8ad8cc3b/src/app/components/utils/objectutils.ts#L7)

Finally I removed the nested data references. But after seeing the error again on screenshot... Could be a workaround to implement a toString function for the data objects.

I'm wondering why primeng compares the whole object. A kind of trackby function could save performance while comparing selected copies with the original data

rubmz commented 7 months ago

For starters they could use lodash.isEqual() which for the best of my knowledge do the work without the circular issue...