top of page
Blog
  • Writer's pictureArtur Romaniuk

Lightning Web Components Developer Guide. Chapter 4: LWC Communication with Apex and Salesforce

This is already our fourth and final article in the LWC series. In the previous ones, we delved into the principles of building components, basic concepts, and specifics of markup in LWC, as well as the communication between the components.


In this chapter of the Lightning Web Components Developer Guide, we explore how to use the built-in LWC methods to interact with Apex controllers and retrieve data from the Salesforce database. Let's get started!

As an example, let's use a basic Apex controller that includes a method called getContacts, which returns a list of contacts.


public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts() {
        return [SELECT Id, FirstName, LastName, Email FROM Contact];
    }
}

To invoke this method, you can use the built-in @wire method in LWC. This annotation is used to perform queries to Salesforce, such as retrieving data from objects or executing searches. It allows you to link the query results with the component's properties. Here's an example of code using the @wire method to fetch data from an Apex controller and display it on the screen.


import { LightningElement, wire } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';

export default class ContactList extends LightningElement {
    @wire(getContacts)
    contacts;

    get hasContacts() {
        return this.contacts && this.contacts.data && this.contacts.data.length > 0;
    }
}

In this example, we use the @wire decorator to call the getContacts method in the ContactController Apex controller. The retrieved data is stored in the contacts variable. The template uses the hasContacts getter, which returns true if contacts are found.


This annotation can also be used for direct interaction with Salesforce. In this instance, we use the @wire annotation to invoke the getRecord function from the lightning/uiRecordApi package. The getRecord function queries Salesforce to retrieve a record with the specified ID and specified fields.


The query result is stored in the account variable, which we can then use in our component. In the get accountName() method, we use the value of the Name field from the retrieved record.


import { LightningElement, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
export default class ExampleComponent extends LightningElement {
    @wire(getRecord, { recordId: '001xxxxxxxxxxxxxxx', fields: ['Account.Name'] })
    account;
    get accountName() {
        return this.account.data.fields.Name.value;
    }
}

The @wire annotation has various capabilities. However, let's also explore its usage in obtaining picklist values for Salesforce objects.


import { LightningElement, wire } from 'lwc';
import { getObjectInfo, getPicklistValues } from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';

export default class PicklistValuesExample extends LightningElement {
    industryPicklistValues = [];

    @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
    accountObjectInfo;

    @wire(getPicklistValues, { recordTypeId: '$accountObjectInfo.data.defaultRecordTypeId', fieldApiName: INDUSTRY_FIELD })
    industryPicklist({ data, error }) {
        if (data) {
            this.industryPicklistValues = data.values;
        } else if (error) {
            // Error handling
        }
    }
}

In this example, the @wire annotation is used to retrieve picklist values for the 'Industry' field of the 'Account' object. Initially, we use getObjectInfo to obtain information about the object, including the default record type. Then, we use getPicklistValues to retrieve the picklist values for the 'Industry' field. The obtained values are stored in the industryPicklistValues variable.


This example demonstrates how the @wire annotation enables easy and efficient retrieval of picklist values for Salesforce object fields for use in your Lightning component.


There is also another way to call Apex methods from LWC that does not use the @wire decorator. For this, you can import the method and invoke it in the LWC component's function. Let's see how this can be done.


Let's go back to our Apex controller and the getContacts method, but this time, we'll invoke it without using the @wire decorator:


import { LightningElement } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';

export default class ContactList extends LightningElement {
    contacts;

    connectedCallback() {
        getContacts()
            .then(result => {
                this.contacts = result;
            })
            .catch(error => {
                console.error(error);
            });
    }
}

In this example, we import the getContacts method from the ContactController Apex controller. In the connectedCallback function, which is called when the component is added to the DOM, we invoke this method and retrieve data from Salesforce. The obtained data is stored in the contacts variable.


This approach does not use the @wire decorator but still allows you to call Apex methods from LWC and retrieve data from the Salesforce database. You can choose the approach that best suits your project and programming style.


Now we know that in LWC, there are two ways to call Apex methods: with the @wire annotation and without it. Both approaches have their advantages and disadvantages.


Advantages of using @wire for calling Apex methods in LWC:


  • @wire automatically handles server requests and updates data when it changes.

  • @wire can be used to make server requests when the LWC component is initialized.

  • @wire can be used to pass data between LWC components.

  • An additional advantage of using @wire is the possibility of automatic data caching, which can improve the performance of components.


Disadvantages of using @wire:


  • @wire is used for asynchronous data retrieval from the server. This means that data may come from the server with some delay. In other words, we cannot guarantee the sequence of calls to different @wire methods. In comparison, with direct import and invocation, where you can call Apex methods sequentially. Therefore, if we planned to use the results of one method's response in the invocation of another, we cannot guarantee the correctness of such operation when using @wire.


  • Sometimes, @wire can be challenging to configure and understand, especially when working with multi-layered objects.


  • Limitations in usage: @wire has limitations on the types of data that can be returned from Apex classes. For example, @wire cannot return collections of maps or objects.


Advantages of using a simple method for invoking Apex from LWC:


  • Calling a method can be less complex to understand, especially when working with simple objects.


  • You can control when the method call will be made. This can improve your application's performance since you will fetch data only when it is actually needed.


  • Calling a method allows you to have more flexibility in controlling the data you pass to and receive from the Apex class. You can use JavaScript objects for passing and receiving data, allowing you to work more flexibly with data and process it on the client side.


Drawbacks of using a simple method call:


  • You have to manually import the methods you plan to use.

  • Requires the creation of an Apex class and methods and cannot leverage standard functionality, such as getRecord.

Therefore, both approaches have their advantages and drawbacks. You can choose the approach that best fits your needs and project requirements.

Regardless of the approach used to invoke Apex methods from LWC, it's important to handle errors that may occur during the request execution.


Here are some general recommendations for handling errors when using LWC to call Apex methods: - Use try-catch blocks to catch errors that may occur during the execution of Apex methods. - Inform users about errors and provide recommended next steps. - Utilize Salesforce logging system to store detailed information about errors that occur during the execution of Apex or LWC methods. - Avoid displaying sensitive information, such as stack traces, to users in production environments.

For example, when using @wire to call Apex methods, you can use a catch block to handle errors. Here's an example code snippet:


import { LightningElement, api, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountList extends LightningElement {
    @api recordId;
    accounts;
    error;

    @wire(getAccounts, { recordId: '$recordId' })
    wiredAccounts({ error, data }) {
        if (data) {
            this.accounts = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.accounts = undefined;
        }
    }
}

In this example, we use a catch block in the wiredAccounts method to catch errors that occur during the execution of the getAccounts method. We also use the error variable to store information about the error and display it to the user.


In a simple method call, we can also use a catch block to handle errors. Here's an example code snippet:


import { LightningElement, api, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountList extends LightningElement {
    @api recordId;
    accounts;
    error;
    loadingSpinner = false;

    handleButtonClick() {
        this.loadingSpinner = true;
        getAccounts({ recordId: this.recordId 
        }).then(result => {
            this.accounts = result;
            this.error = undefined;
        }).catch(error => {
            this.error = error;
            this.accounts = undefined;
        }).finally(() => {
            this.loadingSpinner  = false;
        })
    }
}

In this example, we call the getAccounts method without the @wire annotation. We use the Promise.then and Promise.catch methods to handle the results of the request and any errors that may occur during its execution.


You can also see the finally construct, which is convenient to use in cases where, regardless of the results of the Apex call (including errors), we need to perform certain actions. A good example would be a spinner that indicates to the user that the page is processing data and should disappear after completion, regardless of any errors returned from Apex. In our example, you can see such logic with the loadingSpinner.


So, it's important to handle errors that may occur during the invocation of Apex methods from LWC, regardless of the approach used for their invocation. Handling errors can help ensure the security and stability of your LWC component and the application as a whole.


How to use JSONGenerator in Apex


Let's discuss one of the simplest and convenient ways to format a large amount of data in Apex before returning it to LWC.


JSONGenerator is a class in Apex that allows you to build a JSON object from a set of data represented as a list or map. This is especially useful for returning data from an Apex method to an LWC component in a structured format.


Here's an example demonstrating the use of JSONGenerator to build a JSON object from a list of Account objects in Apex:


public class AccountJSONGenerator {

  @AuraEnabled(cacheable=true)
  public static String getAccountsJSON() {

    List<Account> accounts = [SELECT Id, Name, Industry, Type, BillingState FROM Account LIMIT 10];

    JSONGenerator gen = JSON.createGenerator(true);
    gen.writeStartArray();
    for(Account acc : accounts) {
        gen.writeStartObject();
        gen.writeStringField('id', acc.Id);
        gen.writeStringField('name', acc.Name);
        gen.writeStringField('industry', acc.Industry);
        gen.writeStringField('type', acc.Type);
        gen.writeStringField('billingState', acc.BillingState);
        gen.writeEndObject();
    }
    gen.writeEndArray();

    return gen.getAsString();
  }
}

In this example, the getAccountsJSON() method is called from LWC, and is returned as a string of JSON containing data from the list of Account objects. Initially, a query is made to the database to retrieve a list of Account objects. Then the JSONGenerator class is used to create a JSON object containing this data in a structured format. The final step is to return this JSON string from the getAccountsJSON() method.

In LWC, you can obtain this JSON object through the Apex method and conveniently use it further:


import { LightningElement, wire } from 'lwc';
import getAccountsJSON from '@salesforce/apex/AccountJSONGenerator.getAccountsJSON';

export default class AccountList extends LightningElement {
    accounts;

    @wire(getAccountsJSON)
    wiredAccounts({error, data}) {
        if (data) {
            this.accounts = JSON.parse(data);
        } else if (error) {
            console.error(error);
        }
    }
}

In this example, the getAccountsJSON() method is called from Apex, and the data is passed to the “data” variable, while errors are passed to the “error” variable. Before setting the “data” into the “accounts” variable, it needs to be unpacked from the JSON format using the JSON.parse() function. In case of an error, an error message is displayed in the browser console.


Thus, using JSONGenerator in Apex allows creating a structured JSON object from objects or lists, which can be returned to LWC for further processing.


Conclusion to Lightning Web Components Developer Guide Chapter 4

So, we've explored various methods of interaction between LWC and Apex in Salesforce,  such as:


  1. Using the @wire decorator to retrieve data from Apex methods.

  2. Calling Apex method from LWC by pre-importing these methods.

These methods have their advantages and disadvantages, so it's crucial to consider the context of your project and choose the most suitable way of interaction between LWC and Apex. It's also important to adhere to best practices when using these methods, such as error handling and optimizing server queries.

3. We've also discussed how to use JSONGenerator in Apex to prepare a structured JSON object from objects or lists, which can be returned to LWC for further processing.

This is the final article in Lightning Web Components Developer Guide series covering the fundamentals of LWC. Previously, we covered the basics of LWC components, fundamental principles of markup in LWC, and communication between LWC components. We hope you enjoyed and found them valuable!

bottom of page