Returning an array of objects to lwc from Apex
This post explores the options for returning an object from an Apex class and how we can read and present this.
Lets start with a very simple returning of a list of strings from an Apex Class. Lets start with a simple class called objectReturnController and method to return a list of strings:
public with sharing class objectReturnController { @AuraEnabled(cacheable=true) public static List<String> getListofStrings () { List<String> retStringList = new List<String> (); retStringList.add('Apples'); retStringList.add('Pears'); retStringList.add('Bananas'); retStringList.add('Oranges'); return retStringList; } }
And a lightning web component to call via a wire service:
import { LightningElement, wire } from 'lwc'; import getListofStrings from "@salesforce/apex/objectReturnController.getListofStrings"; export default class ObjectReturn extends LightningElement { returnString; @wire(getListofStrings) wiredgetListofStrings({ error, data }) { if (data) { console.log('-- data returned --'); console.log(data); this.returnString = data; } else if (error) { console.log(error); } } }
Opening the developer tools this will get us:
Lets try returning a simple list of Accounts, so adding this to the apex class:
@AuraEnabled(cacheable=true) public static List<Account> getListofAccounts () { List<Account> retAccountList = new List<Account> (); retAccountList = [SELECT Id, Name FROM Account LIMIT 5]; return retAccountList; }
and this to the lwc;
@wire(getListofAccounts) wiredgetListofAccounts({ error, data }) { if (data) { console.log('-- data returned --'); console.log(data); this.returnAccount = data; } else if (error) { console.log(error); } }
We get in the developer console:
Lets display these in a table. There are plenty of ways to do this but lets manually create the table and then use the for:each to jump through each row (see https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.create_lists):
<lightning-card title="Results From Apex Call" icon-name="custom:custom40"> <template if:true={returnAccount}> <table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_fixed-layout slds-table_striped" > <thead> <tr class="slds-text-title_caps"> <th scope="col"> <div class="slds-truncate slds-text-heading_small" title="Status"> Id </div> </th> <th scope="col"> <div class="slds-truncate slds-text-heading_small" title="Name"> Name </div> </th> </tr> </thead> <tbody> <template for:each={returnAccount} for:item="item"> <tr key={item.Id} class="list-line slds-hint-parent"> <th key={item.Id} scope="row"> <div key={item.Id} class="slds-wrap" title="Id"> {item.Id} </div> </th> <td key={item.Id}> <div class="slds-wrap" title="Name"> {item.Name} </div> </td> </tr> </template> </tbody> </table> </template> </lightning-card>
This renders as below:
Lets imagine that we want to display some additional information in a third column that is not held on the Account object, for instance the month and year that the account was created. Some options on how we could do this:
- Pass back additional information in the returned Account list and then in the lwc use some code to generate a new item of data for us to display
- Instead of returning a List<Account> return a new Object that will show the information we need
- Instead of returning a List<Account> return a JSON object with all the information we need
We will do each of these, but first lets first create the additional column.
<template> <lightning-card title="Results From Apex Call" icon-name="custom:custom40"> <template if:true={returnAccount}> <table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_fixed-layout slds-table_striped" > <thead> <tr class="slds-text-title_caps"> <th scope="col"> <div class="slds-truncate slds-text-heading_small" title="Status"> Id </div> </th> <th scope="col"> <div class="slds-truncate slds-text-heading_small" title="Name"> Name </div> </th> <th scope="col"> <div class="slds-truncate slds-text-heading_small" title="Additional Info"> Additional Info </div> </th> </tr> </thead> <tbody> <template for:each={returnAccount} for:item="item"> <tr key={item.Id} class="list-line slds-hint-parent"> <th key={item.Id} scope="row"> <div key={item.Id} class="slds-wrap" title="Id"> {item.Id} </div> </th> <td key={item.Id}> <div class="slds-wrap" title="Name"> {item.Name} </div> </td> <td key={item.Id}> <div class="slds-wrap" title="Additional Info"> Additional Information goes here </div> </td> </tr> </template> </tbody> </table> </template> </lightning-card> </template>
Option 1 – Pass back additional information in the returned Account list and then in the lwc use some code to generate a new item of data for us to display. This is simple on the apex side, we will simply add in CreatedDate and then pass this in to the lwc component.
@AuraEnabled(cacheable=true) public static List<Account> getListofAccounts () { List<Account> retAccountList = new List<Account> (); retAccountList = [SELECT Id, Name, CreatedDate FROM Account LIMIT 5]; return retAccountList; }
This provides the CreatedDate to the lwc component, we get an array of objects looking like this:
We need to add an additional item to each object with the information we need based on the CreatedDate. As we get an array of objects back we can use a javascript map method on the array. From the documentation at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map the definition of a map method ‘The map()
method creates a new array populated with the results of calling a provided function on every element in the calling array.’ So the following code will run through each item of the array and run the code inside the {}. This code creates a temporary object called dateSt that is made up my extracting from the CreatedDate string. Then we return another object to be added to the array. This object is first made up of the item itself by using …item which is the spread syntax https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax which copies al the elements of the item into the return object and then we add a new property called displayDate which has our string object:
const newData = data.map((item) => { let dateSt = item.CreatedDate.substring(5, 7) + " " + item.CreatedDate.substring(0, 4); return {...item, displayDate: dateSt, } });
So we will get back an object like this:
As a further quick example we have also got access to the index so we could have code like this:
const newData = data.map((item, index) => { let dateSt = item.CreatedDate.substring(5, 7) + " " + item.CreatedDate.substring(0, 4); return {...item, displayDate: dateSt, index: index } });
Which will create
Option 2 – Instead of returning a List<Account> return a new Object that will show the information we need. We can create a class with the elements we need on the server side and then return a List of this new object. So in our controller we create a class and a new method to return a list of this complex object – we will need to modify this as it will not work straightaway.
class ComplexObject { String Id; String Name; Datetime CreatedDate; String displayDate; } @AuraEnabled(cacheable=true) public static List<ComplexObject> getListofComplexAccounts () { List<Account> accountList = new List<Account> (); accountList = [SELECT Id, Name, CreatedDate FROM Account LIMIT 5]; List<ComplexObject> retComplexList = new List<ComplexObject> (); for (Account item: accountList) { ComplexObject comp = new ComplexObject(); comp.Id = item.Id; comp.Name = item.Name; comp.CreatedDate = item.CreatedDate; comp.displayDate = item.CreatedDate.format('MMM YYYY'); retComplexList.add(comp); } return retComplexList; }
In our lwc we can repeat our code to wire into the new apex method
@wire(getListofComplexAccounts) wiredgetListofComplexAccounts({ error, data }) { if (data) { console.log('-- data returned --'); console.log(data); this.returnComplex = newData; } else if (error) { console.log(error); } }
Lets run this and see what we get in the developer console.
We are not getting an object that we can use in the way we were before although it is clear that we are getting the two records in our array. We need to use a string to send the data back from the apex class with our data and we can do this by making a JSON string of our data and then reading that in on the LWC. So our new code looks like this:
@AuraEnabled(cacheable=true) public static String getListofComplexAccounts () { List<Account> accountList = new List<Account> (); accountList = [SELECT Id, Name, CreatedDate FROM Account LIMIT 5]; List<ComplexObject> retComplexList = new List<ComplexObject> (); for (Account item: accountList) { ComplexObject comp = new ComplexObject(); comp.Id = item.Id; comp.Name = item.Name; comp.CreatedDate = item.CreatedDate; comp.displayDate = item.CreatedDate.format('MMM YYYY'); retComplexList.add(comp); } return JSON.serialize(retComplexList); }
and in the lwc
@wire(getListofComplexAccounts) wiredgetListofComplexAccounts({ error, data }) { if (data) { console.log('-- data returned --'); console.log(data); this.returnComplex = JSON.parse(data); } else if (error) { console.log(error); } }
And in the developer console we get a string representation of the object:
Pass back additional information in the returned Account list and then in the lwc use some code to generate a new item of data for us to display
Option 3 – Instead of returning a List<Account> return a JSON object with all the information we need
This example builds on the last example, but instead of using a complex object and then converting into a JSON string to send back, we will create a JSON object straightaway. On or apex controller we use the JSONGenerator methods to create a JSON object as we are going through the accounts.
@AuraEnabled(cacheable=true) public static String getListofComplexAccountsJSON () { List<Account> accountList = new List<Account> (); accountList = [SELECT Id, Name, CreatedDate FROM Account LIMIT 5]; JSONGenerator gen = JSON.createGenerator(true); gen.writeStartArray(); for (Account item: accountList) { ComplexObject comp = new ComplexObject(); gen.writeStartObject(); gen.writeStringField('Id',item.Id); gen.writeStringField('Name',item.Name); gen.writeStringField('CreatedDate',item.CreatedDate.format()); gen.writeStringField('displayDate',item.CreatedDate.format('MMM YYYY')); gen.writeEndObject(); } gen.writeEndArray(); return gen.getAsString(); }
Our lwc is pretty much the same:
@wire(getListofComplexAccountsJSON) wiredgetListofComplexAccountsJSON({ error, data }) { if (data) { console.log('-- data returnComplexJSON returned --'); console.log(data); this.returnComplexJSON = JSON.parse(data); } else if (error) { console.log(error); } }