In LWC, it's now preferable to attempt to use Lightning Data Service first, which includes any of the ui*Api adapters such as getRecord, createRecord and updateRecord:
The simplest way to work with data is to use the base Lightning components built on LDS: lightning-record-form, lightning-record-edit-form, or lightning-record-view-form components.
If you need more customization than those components allow, use @wire to specify a Lightning Data Service wire adapter. Each wire adapter gets a different data shape.
Let's say you've exhausted your options and are looking for a scalable, repeatable way to consistently pass data between client and server. An extremely flexible datatype is then Map<String, Object>
:
lwcTest.html
<lightning-button label="Mutate Data" onclick={mutateData}></lightning-button>
<lightning-button label="Update Complex" onclick={updateComplexData}></lightning-button>
<lightning-button label="Update Account" onclick={updateAccountData}></lightning-button>
lwcTest.js
import { LightningElement, wire, api } from 'lwc';
import wireSimpleOrComplexData from '@salesforce/apex/DataServiceCtrl.wireSimpleOrComplexData';
import updateComplex from '@salesforce/apex/DataServiceCtrl.updateComplex';
import updateAccount from '@salesforce/apex/DataServiceCtrl.updateAccount';
export default class LwcTest extends LightningElement {
@api
get recordId() {
return this._accountId;
}
set recordId(value) {
this._accountId = value;
}
// private
_accountId;
_wiredData;
_account;
_contacts;
_opportunities;
@wire(wireSimpleOrComplexData, { accountId: '$_accountId' })
wiredData(result) {
this._wiredData = result; // to preserve refreshApex if needed
if (result.data) {
// for single sobject object spread works since this is a shallow clone
this._account = { ...result.data.accountFromServer };
// for collections, since every element in array is proxied, we need a deep clone
this._contacts = JSON.parse(JSON.stringify(result.data.contactsFromServer));
// if complex objects are wanted, it might be better to do this at the result.data level
this._opportunities = JSON.parse(JSON.stringify(result.data.opportunitiesFromServer));
console.log(result.data.user.firstName); // UserInfo.getFirstName()
console.log(result.data.system.now); // System.now()
} else {
console.log(result.error);
}
}
// Before reaching this, all the following data had their read only proxies removed
mutateData() {
this._account.Name += ' Updated';
for (let contact of this._contacts) {
contact.Email = contact.LastName + '@test.com';
}
for (let opp of this._opportunities) {
opp.Name += ' Updated';
}
}
updateComplexData() {
const dataToUpdate = {
accountAfterMutate: this._account,
contactsAfterMutate: this._contacts,
opportunitiesAfterMutate: this._opportunities
}
updateComplex({data: dataToUpdate})
.then(result => {
// on success, you can bind to a tracked vars to re-render them
console.log(result);
})
.catch(error => {
console.log(error);
});
}
updateAccountData() {
updateAccount({accountToUpdate: this._account})
.then(result => {
// on success, you can bind to a tracked account variable for template view to re-render the template
console.log(result);
})
.catch(error => {
console.log(error);
});
}
}
DataServiceCtrl.cls
@AuraEnabled (cacheable=true)
public static Map<String, Object> wireSimpleOrComplexData(String accountId) {
Map<String, Object> result = new Map<String, Object>();
// Data 1 single sobject
Account acc = [SELECT Id, Name FROM Account WHERE Id =: accountId LIMIT 1];
result.put('accountFromServer', acc); // otherwise will be an array of 1 if directly SOQL-ed
// Data 2 collections
result.put('contactsFromServer', [SELECT Id, LastName FROM Contact WHERE AccountId =: accountId]);
result.put('opportunitiesFromServer', [SELECT Id, Name FROM Opportunity WHERE AccountId =: accountId]);
// Data 3 nested properties like a POJO
Map<String, String> userInfoMap = new Map<String, String>();
userInfoMap.put('firstName', UserInfo.getFirstName());
Map<String, Object> systemInfoMap = new Map<String, Object>();
systemInfoMap.put('now', System.now());
result.put('user', userInfoMap);
result.put('system', systemInfoMap);
return result;
}
@AuraEnabled
public static Map<String, Object> updateComplex(Map<String, Object> data) {
// Because sobjects were directly used, we can use this serialize/deserialize trick to get it back into a useable state
Account account = (Account) JSON.deserialize(
JSON.serialize(data.get('accountAfterMutate')),
Account.class
);
List<Contact> contacts = (List<Contact>) JSON.deserialize(
JSON.serialize(data.get('contactsAfterMutate')),
List<Contact>.class
);
List<Opportunity> opportunities = (List<Opportunity>) JSON.deserialize(
JSON.serialize(data.get('opportunitiesAfterMutate')),
List<Opportunity>.class
);
// you could put database.saveResult in here if you want
Map<String, Object> updateResults = new Map<String, Object>();
update account;
update contacts;
update opportunities;
updateResults.put('account', account);
updateResults.put('contacts', contacts);
updateResults.put('opportunities', opportunities);
return updateResults;
}
@AuraEnabled
public static Account updateAccount(Account accountToUpdate) {
// no need to serialize/deserialize or transport as JSON here
update accountToUpdate;
return accountToUpdate;
}