Lightning Web Components can call Apex methods from JavaScript for business logic, data creation, updates, and complex queries.
Use @wire for read-only Apex calls and reactive data fetching.
// accountList.js
import { LightningElement, wire, track } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
export default class AccountList extends LightningElement {
@track accounts;
@track error;
@wire(getAccounts)
wiredAccounts({ error, data }) {
if (data) {
this.accounts = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.accounts = undefined;
}
}
}
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List getAccounts() {
return [SELECT Id, Name, Phone, Industry FROM Account LIMIT 50];
}
}
Use imperative Apex when you need actions triggered by user interaction.
// accountForm.js
import { LightningElement, track } from 'lwc';
import saveAccount from '@salesforce/apex/AccountController.saveAccount';
export default class AccountForm extends LightningElement {
@track accountName = '';
@track isLoading = false;
@track error;
handleNameChange(event) {
this.accountName = event.target.value;
}
async handleSave() {
this.isLoading = true;
this.error = undefined;
try {
await saveAccount({ accountName: this.accountName });
this.accountName = '';
} catch (error) {
this.error = error.body ? error.body.message : error.message;
} finally {
this.isLoading = false;
}
}
}
public with sharing class AccountController {
@AuraEnabled
public static Account saveAccount(String accountName) {
Account acc = new Account(Name = accountName);
insert acc;
return acc;
}
}
Pass values from the client to Apex using object literals.
// accountDetails.js
import { LightningElement, track } from 'lwc';
import updateAccount from '@salesforce/apex/AccountController.updateAccount';
export default class AccountDetails extends LightningElement {
@track account = { id: '', name: '', phone: '' };
@track error;
async handleUpdate() {
try {
await updateAccount({
accountId: this.account.id,
accountName: this.account.name,
accountPhone: this.account.phone
});
} catch (error) {
this.error = error.body ? error.body.message : error.message;
}
}
}
public with sharing class AccountController {
@AuraEnabled
public static void updateAccount(Id accountId, String accountName, String accountPhone) {
Account acc = [SELECT Id FROM Account WHERE Id = :accountId LIMIT 1];
acc.Name = accountName;
acc.Phone = accountPhone;
update acc;
}
}
When Apex data changes, use refreshApex to reload wired data.
import { LightningElement, wire, track } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
import deleteAccount from '@salesforce/apex/AccountController.deleteAccount';
export default class AccountManager extends LightningElement {
@track accounts;
@track error;
wiredAccountsResult;
@wire(getAccounts)
wiredAccounts(result) {
this.wiredAccountsResult = result;
if (result.data) {
this.accounts = result.data;
this.error = undefined;
} else if (result.error) {
this.error = result.error;
}
}
async handleDelete(event) {
const accountId = event.target.dataset.id;
try {
await deleteAccount({ accountId });
await refreshApex(this.wiredAccountsResult);
} catch (error) {
this.error = error.body ? error.body.message : error.message;
}
}
}
Always wrap imperative Apex calls with try/catch, and show user-friendly messages.
try {
await saveAccount({ accountName: this.accountName });
} catch (error) {
this.showErrorToast(error);
}
@wire for read-only queries and reactive datarefreshApex() after data mutationstry/catch and user messages