0

I have an Array with multiple Objects, now I want to loop each object as a <tr> in a <table>. I have done that.

However, these objects may contain multiple objects. Then I want to display all objects within 1 object in 1 <td>.

So this is the code what I have currently:

<tr v-for="(data, index) in tableData" :key="index">
  <td
    v-for="(propertyValue, property, propertyIndex) in data.tableRow"
    :key="property"
  >
    <div>
      <span>{{ propertyValue }}</span>
    </div>
  </td>
</tr>
const tableData = [
  {
    tableRow: {
      name: 'test',
    },
    tableRow1: {
      test: 'testComponent',
    },
  },
  {
    tableRow: {
      name: 'test 1',
    },
  },
  {
    tableRow: {
      name: 'test 2',
    },
  },
];

This should also happen for the other td's when there are multiple properties in the 2nd object.

How can I do this?

Can
  • 553
  • 1
  • 9
  • 29

1 Answers1

1

Option 1: Calculate rowComponent per iteration

This can be done with a function that calculates the rowComponent for a particular column:

const getRowComponent = ({ rowComponent }, index) =>
  // Only proceed if rowComponent exists
  rowComponent &&

  // Get the object values of rowComponent as an array
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values
  Object.values(rowComponent)

  // Get array element at specified index (undefined if not found)
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
  .at(index)

The function would be used in the template per item of tableData, using the rowData index (named propertyIndex in this case):

<tr v-for="(data, index) in tableData" :key="index">
  <td v-for="(propertyValue, property, propertyIndex) in data.rowData" :key="property" class="table-body-data">
    <div class="table-body-data-item">
      <span>{{ propertyValue }}</span>
                      
      <span>{{ getRowComponent(data, propertyIndex) }}</span>
    </div>
  </td>
</tr>

demo 1

However, if performance is to be considered (e.g., there are many rows and columns), this solution would be inefficient because:

  • The function is called whenever this component is rendered, which can be multiple times.
  • The function calls Object.values() on the same object for each column, thus repeating work that was done on the previous column.

If there are only a few columns, this hit probably wouldn't matter much.

Option 2: Compute collated data, and render that

A more performant solution is to use a computed property that collates the corresponding rowData and rowComponent, returning an array that better facilitates accessing the paired data than the tableData object:

// https://stackoverflow.com/a/22015930/6277151
const zip = (a, b) => Array.from(Array(Math.max(b.length, a.length)), (_, i) => [a[i], b[i]])
const zipValues = (a, b) => zip(Object.values(a ?? {}), Object.values(b ?? {}))

// Computed table is an array of rows.
// Each row is an array of columns.
// Each column is an array of cell text.
const computedTableData = computed(() => tableData.map(item => zipValues(item.rowData, item.rowComponent)))

Then use the computed array in the template instead of tableData:

<tr v-for="(row, rowIndex) in computedTableData" :key="rowIndex">
  <td v-for="(col, colIndex) in row" :key="colIndex" class="table-body-data">
    <div class="table-body-data-item">
      <span v-for="(cellText, cellTextIndex) in col" :key="cellTextIndex">{{ cellText }}</span>
    </div>
  </td>
</tr>

demo 2

tony19
  • 125,647
  • 18
  • 229
  • 307