Create an LWC Data Table With Nested Iterators in Salesforce

In Salesforce, using LWC components, we create a data table with Nested Iterators to display hierarchical or grouped data within a data table.

The Lightning data table utilises nested iterators to render rows and columns, and fetches data via Apex. After deploying it on the record page, it displays related records with customizable fields and labels.

In this Salesforce tutorial, we will learn how to create an LWC data table with nested iterators in Salesforce.

Use of Nested Iterators in Salesforce Lightning Data table?

In Salesforce, by using the nested iterators, we can create a reusable Lightning Web Component (LWC) that displays a dynamic table of records on a Salesforce object record page.

Nested iterators in an LWC data table use ‘for:each’ directive to build a dynamic table. The outer iterator for:each loops‘ over a list of records, generating table rows (<tr> elements) for each entry.

The inner iterator ‘for:each’ iterates over field names or columns, creating cells (<td> elements) for each field in the current record.

This structure allows the table to display data according to the number of rows and columns based on data and configuration. It automatically updates the records using a unique key, such as a record ID.

Create a Reusable LWC Data Table with Nested Iterators in Salesforce

In the example below, we will create a reusable data table that will display related records on the current record page. In this data table, the user will be able to specify the object, filter field, columns, and labels via Lightning App Builder.

For this Lightning data table, we will create a Parent Component ( customTable) to handle data fetching and table structure. Child Component (customTableColumn) to render individual cell values and a controller class to dynamically fetch the related records.

Now, follow the steps below to create a reusable LWC data table with nested iterators.

1. First, we will create an Apex controller class to fetch the current object’s related records dynamically.

public with sharing class CustomTableController {
    
    @AuraEnabled(cacheable = true)
    public static List<sObject> fetchRecords(String strRecordId, String strField, String strObjectName, String strColumns) {

        String strSOQL = 'SELECT Id, ' + String.escapeSingleQuotes(strColumns) + 
                         ' FROM ' + String.escapeSingleQuotes(strObjectName) + 
                         ' WHERE ' + String.escapeSingleQuotes(strField) + 
                         ' = :strRecordId';

        System.debug('SOQL is: ' + strSOQL);

        return Database.query(strSOQL);
    }
}

2. Create a child LWC component, customTableColumn; this component will render individual cell values in the table.

  • Enter the code below in the HTML file for the child component.
<template>
    {strValue}
</template>
  • To define the rendering logic of related records, enter the code below in the JS file of the child component.
import { LightningElement, api } from 'lwc';

export default class CustomTableColumn extends LightningElement {

    @api column;
    @api record;
    strValue

    connectedCallback() {

        this.strValue = this.record[ this.column ];
    }
}

We will call this child component in the parent one, so there is no need to expose it to the Lightning record page and make changes to the meta.xml file.

3. Create the parent component “customTable” and enter the code below in the HTML file.

To render the data dynamically in the data table, we have used the ‘for:each’ directive is used to iterate over an array and render a template for each item.

Nested iterators refer to placing one ‘for:each’ loop inside another, allowing us to iterate over a collection of rows and, for each item, iterate over another column.

The output will be in the grid form suitable for displaying data in the LWC data table.

<template>
    <lightning-card>
        <table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_col-bordered">
            <thead>
                <tr>
                    <template for:each={labelsArr} for:item="col">
                        <th key={col}>{col}</th>
                    </template>
                </tr>
            </thead>
            <tbody>
                <template for:each={listRecs} for:item="rec">
                    <tr key={rec.Id}>
                        <template for:each={colsArr} for:item="col">
                            <td key={col}><c-custom-table-column column={col} record={rec}></c-custom-table-column></td>
                        </template>
                    </tr>
                </template>
            </tbody>
        </table>
    </lightning-card>
</template>

In the above HTML template, we have used the outer iterator ‘for:each={listRecs}’ to loop over the array of records (listRecs) returned from Apex.

Then we have used the inner Iterator ‘for:each={colsArr}’ that loops over the array of column field names (‘colsArr’) derived from the ‘columns’ property.

  • After this, enter the code below in the JS file of the parent component.
import { LightningElement, api, wire } from 'lwc';
import fetchRecs from '@salesforce/apex/CustomTableController.fetchRecords';

export default class CustomTable extends LightningElement {
    @api recordId;
    @api columns;
    @api field;
    @api objName;
    @api labels;
    listRecs;
    error;

    get colsArr() {
        return this.columns ? this.columns.split(",").map(item => item.trim()) : [];
    }

    get labelsArr() {
        return this.labels ? this.labels.split(",").map(item => item.trim()) : [];
    }

    @wire(fetchRecs, { 
        strRecordId: '$recordId', 
        strField: '$field', 
        strObjectName: '$objName', 
        strColumns: '$columns' 
    })  
    wiredRec({ error, data }) {
        if (data) {
            console.log('Records are ' + JSON.stringify(data));
            this.listRecs = data;
            this.error = null;
        } else if (error) {
            this.listRecs = null;
            this.error = error;
            console.error('Error fetching records: ' + JSON.stringify(error));
        }
    }
}
  • To expose the LWC component visible to the Lightning records page and to map the input values from the controller class that will be entered dynamically in the app builder, enter the code below in the meta.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>63.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <property name="columns" label="Columns" type="String" />
            <property name="field" label="Field" type="String" />
            <property name="objName" label="Object Name" type="String" />
            <property name="labels" label="Labels" type="String" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

4. Now, navigate to the record page, on which you have to display the related records of the object record dynamically.

In this example, I will display related contacts of the accounts records; I will deploy this component on the Account record page.

5. The LWC component is reusable, so the table will be blank. Now, we will define the table columns in the lightning app builder to display the records on the table.

For example, to display related contacts of the current account, the component will be configured in the following way.

  • Columns = FirstName, LastName, Email
  • Field = AccountId
  • Object Name = Contact
  • Labels = First Name, Last Name, Email
Create a resuable LWC table with custom Iterators

After entering the value of Columns, Fields, related Object Name, and the column Labels, we will see the related contacts on the data table.

Let’s take another example where we will display related cases for the current account. For that, the LWC component will be configured in the following way.

  • Columns = CaseNumber, Subject, Status
  • Field = AccountId
  • Object Name = Case
  • Labels = Case, Subject, Status
Custom Table with nested iterator using LWC in Salesforce

This way, we can use the nested iterators in the Salesforce Lightning data table and display related records data by dynamically defining the object fields and columns.

Conclusion

In this Salesforce tutorial, we learned how to create a reusable LWC data table with nested iterators in Salesforce. By using nested ‘for:each’ loops. With nested iterator, we displayed hierarchical or grouped data in a table format.

In the above steps, we created a parent component, customTable, to handle the data fetching and table structure. Then, we rendered individual cell values using the child component, customTableColumn.

Using this LWC component, we can display related records of any of the Salesforce objects by defining the object fields, columns, and labels.

You may also like to read:

Agentforce in Salesforce

DOWNLOAD FREE AGENTFORCE EBOOK

Start with AgentForce in Salesforce. Create your first agent and deploy to your Salesforce Org.

Salesforce flows complete guide

FREE SALESFORCE FLOW EBOOK

Learn how to work with flows in Salesforce with 5 different real time examples.