Data Tables in LWC with related fields

By now you would have noticed that displaying data from related objets into a single data table can be a little bit tricky in Lightning Web Component.

Data Tables in LWC gets painful to work with when you want to display the data from related objets into a single data table.

Data tables in LWC showing data from related objects

The bottle neck that we face here is when we get the data back, that’s going to be in nested json format.

There are a couple of things on which we need to work on to get the Data Tables in LWC to work as expected.

  • The response that we get from Apex method should resemble the json we declared for the columns in data table. Something like this ..
const columns = [
    { label: 'Label', fieldName: 'Name' },
    { label: 'Website', fieldName: 'LeadSource' },
    { label: 'Phone', fieldName: 'Phone', type: 'phone' },
    { label: 'Account Name', fieldName: 'Account.Name'},
    { label: 'Account Industry', fieldName: 'Account.Industry'}
    
];
  • The response we get will be similar to the format shown below and we need to flatten it to resemble the json declared for the columns. So the below json response should be flattened to ..
{
    "Id": "0032v00002lLzgQAAS",
    "Name": "Rose Rose",
    "Phone": "(512) 757-6000",
    "LeadSource": "Trade Show",
    "AccountId": "0012v00002M0O2CAAV",
    "Account": {
      "Name": "Edge Communications",
      "Industry": "Electronics",
      "Id": "0012v00002M0O2CAAV"
    }
}

Typical response when we invoke a SOQL query

something like this ..

{
    "Id": "0032v00002lLzgQAAS",
    "Name": "Rose Rose",
    "Phone": "(512) 757-6000",
    "LeadSource": "Trade Show",
    "AccountId": "0012v00002M0O2CAAV",
    "Account.Name": "Edge Communications",
    "Account.Industry": "Electronics",
    "Account.Id": "0012v00002M0O2CAAV"     
}

The response we received has to be flattened like this

Okay! Let's get started.

This is how my template file is going to look like.

<template>
    <div style="height: 500px;">
        <lightning-datatable
                key-field="id"
                data={allContacts}
                columns={columns}>
        </lightning-datatable>
    </div>    
</template>

exploreCustomDataTable.html

Then comes the js file.

import { LightningElement, wire, track } from 'lwc';

import getContacts from '@salesforce/apex/ExploreCustomContactController.getContacts';

const columns = [
    { label: 'Label', fieldName: 'Name' },
    { label: 'Website', fieldName: 'LeadSource' },
    { label: 'Phone', fieldName: 'Phone', type: 'phone' },
    { label: 'Account Name', fieldName: 'Account.Name'},
    { label: 'Account Industry', fieldName: 'Account.Industry'}
    
];

export default class ExploreCustomDataTable extends LightningElement {

    @track allContacts = [];
    @track columns = columns;
    
    //wiring an apex method to a function
    @wire(getContacts)
    wiredContacts({ error, data }) {
        if(data) {
           //this is the final array into which the flattened response will be pushed. 
           let contactsArray = [];
            
           for (let row of data) {
                // this const stroes a single flattened row. 
                const flattenedRow = {}
                
                // get keys of a single row — Name, Phone, LeadSource and etc
                let rowKeys = Object.keys(row); 
               
                //iterate 
                rowKeys.forEach((rowKey) => {
                    
                    //get the value of each key of a single row. John, 999-999-999, Web and etc
                    const singleNodeValue = row[rowKey];
                    
                    //check if the value is a node(object) or a string
                    if(singleNodeValue.constructor === Object){
                        
                        //if it's an object flatten it
                        this._flatten(singleNodeValue, flattenedRow, rowKey)        
                    }else{
                        
                        //if it’s a normal string push it to the flattenedRow array
                        flattenedRow[rowKey] = singleNodeValue;
                    }
                    
                });
               
                //push all the flattened rows to the final array 
                contactsArray.push(flattenedRow);
            }
            
            //assign the array to an array that's used in the template file
            this.allContacts = contactsArray;
        } else if (error) {
            this.error = error;
        }
    }
    
    /* create keys in the format of Account.Id, Account.Rating, Account.Industry and etc
    
    we can avoid using this function by reusing the above function. 
    
    To understand in easily I used a separate function 
    
    Feel free to refactor it */
    _flatten = (nodeValue, flattenedRow, nodeName) => {        
        let rowKeys = Object.keys(nodeValue);
        rowKeys.forEach((key) => {
            let finalKey = nodeName + '.'+ key;
            flattenedRow[finalKey] = nodeValue[key];
        })
    }

}

exploreCustomDataTable.js

It's not that we can only show data from two related objects. We can also show data from multiple objects that are in a relationship, as long as the flattened JSON response resembles the JSON we used in declaring the columns.

Liked the post? Let me know what you think of it.