Solución:
En LWC, ahora es preferible intentar usar Lightning Data Service primero, que incluye cualquiera de los adaptadores ui * Api como getRecord, createRecord y updateRecord:
La forma más sencilla de trabajar con datos es utilizar los componentes Lightning base construidos en LDS: lightning-record-form, lightning-record-edit-form o lightning-record-view-form componentes.
Si necesita más personalización de la que permiten esos componentes, use @wire para especificar un adaptador de cable Lightning Data Service. Cada adaptador de cable tiene una forma de datos diferente.
Supongamos que ha agotado sus opciones y está buscando una forma escalable y repetible de pasar datos de manera consistente entre el cliente y el servidor. Un tipo de datos extremadamente flexible es entonces 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;
}
Comencé a mirar a mi alrededor y seguí el ejemplo del tema Migrar Apex, que dice:
Los componentes Aura y los componentes web Lightning utilizan un controlador Apex para leer o persistir Datos de Salesforce. No hay diferencias de sintaxis para los dos modelos de programación.
Entonces, según cómo funcionó en Lightning Aura Components, intenté ver si también funcionaba en LWC, y sí, funciona.
En resumen, necesitaba asegurarme de representar al SObject
record / data como JSON y luego pásalo como parámetro.
A continuación se muestra el código de muestra que funcionó para mí, donde pude construir un JSON / manipular un JSON existente y luego pasarlo como parámetro a un método Apex personalizado para crear / actualizar un registro.
HTML
<lightning-card title="My Hello World" icon-name="standard:contact">
{recordId}
</lightning-card>
<lightning-button label="Create Record" onclick={createRecord}></lightning-button>
<lightning-button label="Update Record" onclick={udpateRecord}></lightning-button>
JavaScript
import createContactRecord from '@salesforce/apex/ContactController.createContactRecord';
import updateContactRecord from '@salesforce/apex/ContactController.updateContactRecord';
import myContact from "@salesforce/apex/ContactController.fetchContact";
....
@track recordId;
contactRecord;
// fetches a contact record from Apex
@wire (myContact)
fetchedContact({error, data}){
if(data){
// this is where I save the fetched contact which will be updated later
this.contactRecord = JSON.stringify(data);
...
...
}
...
}
// my create record JS function, where I construct a SObject and am able to
// successfully create a record
createRecord() {
// created a JSON representation of the Contact record,
// same as we would do in Lightning Aura Components
let cont = { 'sobjectType': 'Contact' };
cont.FirstName="Jayant";
cont.LastName="From LWC";
createContactRecord({newRecord: cont})
.then(result => {
this.recordId = result;
console.log(result);
})
.catch(error => {
console.log(error);
this.error = error;
});
}
// my update record JS function, where I manipulate the JSON
// and set some values to be able to successfully update the record
updateRecord() {
let cont = JSON.parse(this.contactRecord);
// update the fields those are required to be updated
cont.LastName="-LWC1";
updateContactRecord({recordForUpdate: cont})
.then(result => {
this.wiredContact = result;
console.log(result);
})
.catch(error => {
console.log(error);
this.error = error;
});
}
Apéndice
@AuraEnabled(cacheable=true)
public static Contact fetchContact(){
return [SELECT Id,Name, LastName FROM Contact where Id='xxxx' LIMIT 1];
}
@AuraEnabled
public static String createContactRecord(Contact newRecord){
insert newRecord;
return newRecord.Id;
}
@AuraEnabled
public static String updateContactRecord(Contact recordForUpdate){
update recordForUpdate;
return recordForUpdate.Name;
}
Aquí está mi código que utilizo para los registros CRU en LWC. Ejemplo bastante básico. No estoy usando la manipulación de String o JSON. También estoy usando enlaces estáticos usando importaciones de fieldName
HTML:
<lightning-input label="FirstName" value={realFormData.FirstName} if:true={realFormData} onchange={updateValue} data-field="FirstName"></lightning-input>
<lightning-input label="LastName" value={realFormData.LastName} if:true={realFormData} onchange={updateValue} data-field="LastName"></lightning-input>
{recordId} <br/>
<button onclick={saveRecord}> Save Record</button>
<br/>
<button onclick={createRecord}> Create new hardcored CONTACT Record and load in UI</button>
‘
JS:
import { LightningElement ,wire,track,api } from 'lwc';
import getMyContact from "@salesforce/apex/ContactController.fetchContact";
import updateMyContact from "@salesforce/apex/ContactController.updateContact";
import createMyContact from "@salesforce/apex/ContactController.createContact";
import { refreshApex } from '@salesforce/apex';
import CONTACT_FIRSTNAME from '@salesforce/schema/Contact.FirstName';
import CONTACT_LASTNAME from '@salesforce/schema/Contact.LastName';
export default class MyCmp extends LightningElement {
@api wiredContact;
@api recordId;
@api realFormData;
@wire (getMyContact , { contactId: '$recordId' })
fetchedContact( resp){
this.wiredContact = resp;
this.realFormData = {... this.wiredContact.data};
}
updateValue(event){
this.realFormData = {...this.realFormData , [event.target.dataset.field] : event.detail.value};
console.log( this.realFormData);
}
saveRecord(event ){
updateMyContact({con : this.realFormData}).then(()=>{
console.log('Refresh Apex called');
refreshApex(this.wiredContact);
});
}
createRecord(event ){
let newContact = { [CONTACT_FIRSTNAME.fieldApiName] : 'Pikachdu' ,[CONTACT_LASTNAME.fieldApiName] : 'Raichu' };
createMyContact({con : newContact}).then((resp)=>{
this.recordId = resp.Id; //this will auto call wireMethod/
}).catch((err) => {
// Handle any error that occurred in any of the previous
// promises in the chain.
console.log(JSON.stringify(err));
});
}
}
Apéndice:
public class ContactController {
@AuraEnabled(cacheable=true)
public static Contact fetchContact(Id contactId){
return [SELECT Id,FirstName,LastName FROM COntact where id=:contactId LIMIT 1];
}
@AuraEnabled
public static void updateContact(Contact con){
update con;
}
@AuraEnabled
public static contact createContact(Contact con){
insert con;
return con;
}
}