Understanding Wire vs Imperative Apex Method Calls in Salesforce Lightning Web Components (LWC)

Introduction:

Salesforce Lightning Web Components (LWC) provides two ways to call Apex methods – wire and imperative, so it’s very important to Understand Wire vs Imperative Apex Method Calls in LWC. Both wire and imperative methods in Salesforce LWC are used to retrieve and manipulate data from the server side. This blog post will make you Understand Wire vs Imperative Calls in LWC and also explain the differences between the wire and imperative calls, their pros and cons, and practical use cases for each.

Section 1: Understanding Wire and Imperative Calls

In Salesforce LWC, Apex methods can be called using two different methods – wire and imperative. Understanding the differences between these two methods is essential for building efficient and effective LWCs.

a) Definition of wire and imperative calls in LWC:

  1. Wire calls enable the real-time retrieval of data from the server side. Whenever the data changes on the server, the wire call automatically updates the data in the LWC.
  2. On the other hand, imperative calls are used to retrieve data from the server side only when needed, and the data is not updated automatically.

b) How Wire and Imperative calls work in LWC:

Wire calls work by connecting the LWC with the server-side data source using an adapter. The adapter specifies which Apex method to call and which data to retrieve. Whenever the data changes on the server side, the adapter automatically updates the data in the LWC. Whenever the LWC loads or updates, it automatically executes the wire call and retrieves the data in real time.

For Example – This LWC retrieves contact data related to an account using the “fetchData” Apex method. The @wire decorator creates a wire adapter to automatically update the component’s properties with the retrieved data or error.


mport { LightningElement, wire, api} from 'lwc';
import fetchData from '@salesforce/apex/MyController.fetchData';
 
export default class ExampleComponent extends LightningElement {
    @api recordId;
    contacts;
    error;
     
    @wire(fetchData,{accId: '$recordId'})
    wiredFetchData({ error, data }) {
        if (data) {
            this.contacts = data;
            this.error = undefined;
        } else if (error) {
            this.contacts = undefined;
            this.error = error;
        }
    }
}

Imperative calls, on the other hand, work by explicitly calling an Apex method using JavaScript. Imperative calls retrieve data from the server side only when needed, such as on page load, button click, or when called from another method. The component retrieves the data from the server side only when requested and not automatically updated. The LWC stores the retrieved data in a variable and can use it as required.

For Example – This LWC retrieves data from an Apex method named “fetchData” imperatively. Here, connectedCallback lifecycle hook is used to call the Apex method imperatively when the component is connected to the DOM.


import { LightningElement } from 'lwc';
import fetchData from '@salesforce/apex/MyController.fetchData';
 
export default class ExampleComponent extends LightningElement {
    data;
 
    connectedCallback() {
        fetchData()
            .then(result => {
                this.data = result;
            })
            .catch(error => {
                console.error(error);
            });
    }
}
c) Differences between the wire and imperative calls:
  1. One of the main differences between the wire and imperative calls is how data is retrieved and updated. Wire calls provide real-time updates to the data in the LWC, while imperative calls require the developer to explicitly retrieve and update the data.
  2. Another difference between the two methods is how the data is stored. With wire calls, the data is stored in a reactive variable that updates automatically whenever the data on the server-side changes. With imperative calls, the data is stored in a regular variable and needs to be updated manually.
d) Pros and cons of using wire and imperative calls:
Pros of using wired apex method call:
  • Automatic data refreshing: Wired apex method calls provide automatic data refreshing, meaning that any changes made to the data are automatically reflected in the UI without requiring any additional code.
  • No need for error handling: Since the wired apex method calls handle errors automatically, you don’t need to write any additional code for error handling.
  • Better performance: Wired apex method calls use a reactive programming model and optimize the data transfer between the server and client, resulting in better performance.
Cons of using wired apex method call:
  • Limited data manipulation: Wired apex method calls are read-only and do not allow for any data manipulation or updates.
  • Limited control over the data: Wired apex method calls automatically fetch and cache data, which can result in less control over the data and its behavior.
  • Limited support for complex queries: Wired apex method calls only support simple queries and may not be suitable for more complex queries that require additional logic.
Pros of using imperative apex method call:
  • Full control over data: Imperative apex method calls allow full control over data and its behavior, allowing for DML operations such as updates, inserts, and deletes as needed.
  • Support for complex queries: Imperative apex method calls allow for complex queries and additional logic, making them suitable for more complex use cases.
  • Better debugging: Since the imperative apex method calls to provide more control over the data, they also allow for easier debugging of issues.
Cons of using imperative apex method call:
  • More error-prone: Imperative apex method calls require more error handling code since they do not handle errors automatically as wired apex method calls.
  • Manual data refreshing: Since imperative apex method calls do not provide automatic data refreshing, you need to write additional code to manually refresh the data.
  • Lower performance: Imperative apex method calls require more code and data transfer between the server and client, resulting in potentially lower performance compared to wired apex method calls.

Section 2: Using Wire and Imperative Calls in Apex Method Calls

a) Importing Apex Methods in LWC

To import an Apex method for use in LWC, we should use the default import syntax in JavaScript via the @salesforce/apex scoped packages, as shown below:

1
import apexMethodName from '@salesforce/apex/Namespace.Classname.apexMethodReference';
  • apexMethodName—A symbol that identifies the Apex method.
  • apexMethodReference—The name of the Apex method to import.
  • Classname—The name of the Apex class.
  • Namespace—If the class is in the same namespace as the component, don’t specify a namespace. If the class is in a managed package, specify the namespace of the managed package.
b) How to use wire and imperative calls in Apex method calls in LWC

Apex methods are used in Salesforce to perform complex computations, data manipulation, and DML operations. Lightning Web Components (LWC) allow developers to invoke Apex methods using either the wire service or imperative calls. In this section, we will explore how to use wire and imperative calls in Apex method calls in LWC.

To call an Apex method, a Lightning web component can:

1. Wire a property:

Developers often wire an Apex method as a property for simple use cases without extra logic. It’s shorter and easier to read, ideal for small components without much customization. However, it may not be suitable for more complex use cases that require additional logic or error handling.

If you decorate a property with @wire, it returns the results to the property’s data or error property.

Syntax –


import apexMethodName from '@salesforce/apex/Namespace.Classname.apexMethodReference';
 
@wire(apexMethod, { apexMethodParams }) propertyName;

Example – This Lightning Web Components (LWC) retrieves contact records related to a specific Account record and displays them in a table format. It uses a wire service to fetch data from the server-side Apex controller method named getContacts in the ContactController class. By using “$” in the parameter of the wire property, we make it reactive, so any time the value of the parameter “recordId” changes, the wire will automatically call the apex method again to retrieve and update the data.

apexWireProperty.html


emplate>
    <lightning-card
        title="ApexWirePropertyExample "
        icon-name="custom:custom63">
        <div
            class="slds-text-heading_small slds-text-align_left"
            style="padding-left: 15px">
            Get Contacts of An Account
        </div>
        <div class="slds-var-m-around_medium">
            <template lwc:if={hasContacts}>
                <table
                    class="slds-table slds-table_bordered slds-table_cell-buffer">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Email</th>
                            <th>Phone</th>
                        </tr>
                    </thead>
                    <tbody>
                        <template for:each={contacts.data} for:item="contact">
                            <tr key={contact.Id}>
                                <td>{contact.Name}</td>
                                <td>{contact.Email}</td>
                                <td>{contact.Phone}</td>
                            </tr>
                        </template>
                    </tbody>
                </table>
            </template>
            <template lwc:elseif={contacts.error}>
                {contacts.error.body.message}
            </template>
            <template lwc:else> No matching Data Found.... </template>
        </div>
    </lightning-card>
</template>

apexWireProperty.js


// Import the LightningElement class , the wire and the api decorator from the lwc module
import { LightningElement, wire, api } from 'lwc';
// Import the getContacts method from the ContactController Apex class
import fetchData from '@salesforce/apex/ContactController.getContacts';
// Declare the ApexWireProperty class as the default export
export default class ApexWireProperty extends LightningElement {
    @api recordId; //the record id of account
    //using the @wire decorator to connect to the getOpportunities Apex method
    // and assinging the result in contacts list
    @wire(fetchData,{accId: '$recordId'}) contacts;
    /**
     * hasContacts method is used to return boolean true or false,
       if contacts properties has data or not.
     * @return boolean true or false
     */
    get hasContacts() {
        return this.contacts && this.contacts.data && this.contacts.data.length > 0;
    }
}

apexWireProperty.js-meta.xml


<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>56.0</apiVersion>
     <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
        <target>lightning__Tab</target>
    </targets>
</LightningComponentBundle>

ContactController.cls


/*
 * This class is used as an controller class for apexWireProperty LWC.
 */
public with sharing class ContactController {
    /**************************************************************************************
    * @Description : The getContacts returns a list of contact objects,
    *                based on the record id of account.
    * @Param       : acc - record id of account
    * @Return      : List<Contact> getContacts
    **************************************************************************************/
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts(Id accId) {
        return [SELECT Id, Name, Email, Phone
                FROM Contact WHERE AccountId=: accId
                WITH SECURITY_ENFORCED
                ORDER BY Name LIMIT 10];
    }
}
Demo ApexWirePropertyExample
2. Wire a function:

Wiring the apex method using the function, offers more control and customization over the wired function. It allows you to handle errors and manipulate data in a more fine-grained way.

If you decorate a function with @wire, it returns the results in an object with a data property or an error property.

Syntax –


import apexMethodName from '@salesforce/apex/Namespace.Classname.apexMethodReference';
 
@wire(apexMethod,{ apexMethodParams })
functionname({ error, data }) {
    if (data) {
         
    } else if (error) {
         
    }
}

Example – This Lightning Web Components (LWC) retrieves matching opportunity records based on the user-entered opportunity stage and the amount and displays them in a table format. It uses a wire service as a function to fetch data from the server-side Apex controller method named getOpportunities in the OpportunityController class. The getOpportunities method returns the list of opportunities if the opportunity stage matches the searchStage and the amount is greater than equal to searchAmount. By using “$” in the parameter of the wire function, we make it reactive, so any time the value of the parameter “recordId” changes, the wire will automatically call the apex method again to retrieve and update the data.

apexWireMethodWithParams.html

<template>
    <lightning-card
        title="ApexWireMethodWithParamsExample "
        icon-name="custom:custom63">
        <div
            class="slds-text-heading_small slds-text-align_left"
            style="padding-left: 15px">
            Get Matching Opportunities with Stage Name and Amount
        </div>
        <div class="slds-var-m-around_medium">
            <lightning-combobox
                label="Opportunity Stage"
                options={stageOptions}
                onchange={handleStageChange}>
            </lightning-combobox>
            <lightning-input
                type="number"
                onchange={handleKeyChange}
                class="slds-var-m-bottom_small"
                label="Amount"
                placeholder="Enter Amount to Search Opps..."
                value={searchAmount}>
            </lightning-input>
            <template lwc:if={hasOpps}>
                <table
                    class="slds-table slds-table_bordered slds-table_cell-buffer">
                    <thead>
                        <tr>
                            <th>Opportunity Name</th>
                            <th>Opportunity Stage</th>
                            <th>Opportunity Amount</th>
                        </tr>
                    </thead>
                    <tbody>
                        <template
                            for:each={opportunities}
                            for:item="opportunity">
                            <tr key={opportunity.Id}>
                                <td>{opportunity.Name}</td>
                                <td>{opportunity.StageName}</td>
                                <td>{opportunity.Amount}</td>
                            </tr>
                        </template>
                    </tbody>
                </table>
            </template>
            <template lwc:elseif={error}>{error.body.message} </template>
            <template lwc:else> No matching Data Found.... </template>
        </div>
    </lightning-card>
</template>

apexWireMethodWithParams.js


// Import the LightningElement class and the wire decorator from the lwc module
import { LightningElement, wire } from "lwc";
// Import the getOpportunities method from the OpportunityController Apex class
import getOpportunities from "@salesforce/apex/OpportunityController.getOpportunities";
/** The delay used when debouncing event handlers before invoking Apex. */
const DELAY = 300;
// Declare the ApexWireMethodWithParams class as the default export
export default class ApexWireMethodWithParams extends LightningElement {
    // Defining class properties
    opportunities;
    error;
    searchAmount = "";
    searchStage;
    // Defining the stage options as an array of objects
    stageOptions = [
        { label: "---None---", value: "" },
        { label: "Prospecting", value: "Prospecting" },
        { label: "Qualification", value: "Qualification" },
        { label: "Value Proposition", value: "Value Proposition" },
        { label: "Closed Won", value: "Closed Won" },
        { label: "Closed Lost", value: "Closed Lost" }
    ];
    //using the @wire decorator to connect to the getOpportunities Apex method
    @wire(getOpportunities, { stage: "$searchStage", amount: "$searchAmount" })
    /**
     * wiredOpportunities method is used to handle the
       data returned from the Apex method.
     * It assigns the data to the opportunities property.
     * It also assigns the error to error property in case of error.
     * @param {Object} data - data returned from the Apex method
     * @param {Object} error - error returned from the Apex method
     */
    wiredOpportunities({ error, data }) {
        if (data) {
            this.opportunities = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.opportunities = undefined;
        }
    }
    /**
      * handleKeyChange method is used to handle the change of amount field
        and debouncing it to avoid a large number of Apex method calls.
      * It assigns the searchAmount property to user entered opportunity amount value.
      * @param {Object} event - event data
      */
    handleKeyChange(event) {
        // Debouncing this method: Do not update the reactive property as long as this function is
        // being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
        window.clearTimeout(this.delayTimeout);
        const searchAmount = event.target.value;
        // eslint-disable-next-line @lwc/lwc/no-async-operation
        this.delayTimeout = setTimeout(() => {
            this.searchAmount = searchAmount;
        }, DELAY);
    }
    /**
     * hasOpps method is used to return boolean true or false,
       if opportunites properties has data or not.
     * @return boolean true or false
     */
    get hasOpps() {
        return this.opportunities && this.opportunities.length > 0;
    }
    /**
     * handleStageChange method is used to handle the change event of stage field.
     * It assigns the searchStage property to user selected opportunity stage value.
     * @param {Object} event - event data
     */
    handleStageChange(event) {
        this.searchStage = event.detail.value;
    }
}

apexWireMethodWithParams.js-meta.xml


<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>56.0</apiVersion>
     <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
        <target>lightning__Tab</target>
    </targets>
</LightningComponentBundle>

OpportunityController.cls


/*
 * This class is used as an controller class for apexWireMethodWithParams LWC.
 */
public with sharing class OpportunityController {
    /**************************************************************************************
    * @Description : The getOpportunities returns a list of Opportunity objects,
    *                based on the user selected stage and amount.
    * @Param       : stage - user selected stage
    * @Param       : scope - user entered amount
    * @Return      : List<Opportunity> opportunities
    **************************************************************************************/
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getOpportunities(String stage, String amount) {
        List<Opportunity> opportunities = new List<Opportunity>();
        if(String.isNotEmpty(amount) && String.isNotEmpty(stage)){
            Integer amt = Integer.valueOf(amount);
            opportunities = [SELECT Id, Name, StageName, Amount, CloseDate
                                    FROM Opportunity
                                    WHERE StageName=: stage
                                    AND Amount >= :amt
                                    WITH SECURITY_ENFORCED];
        }       
        return opportunities;
    }
}
Demo ApexWireMethodWithParamsExample

NOTE: The data property and the error property are hardcoded values in the API. You can only use these values.

3. Call a method imperatively

Imperative calls to Apex methods are when you explicitly call a method and handle the returned results in your code. This approach gives you more control over the data retrieval and manipulation process.

Example – This Lightning Web Components (LWC) retrieves matching opportunity records based on the user-entered opportunity stage and the amount and displays them in a table format. It uses an imperative method call to fetch data from the server-side Apex controller method named getOpportunities in the OpportunityController class. The getOpportunities method returns the list of opportunities on page load based on default searchStage and searchAmount and on-demand on the click of the Get Opportunites button, when the opportunity stage matches the searchStage and the amount is greater than equal to searchAmount.

apexImperativeMethodWithParams.html


<template>
    <lightning-card
        title="ApexImperativeMethodWithParams"
        icon-name="custom:custom63">
        <div
            class="slds-text-heading_small slds-text-align_left"
            style="padding-left: 15px">
            Get Matching Opportunities with Stage Name and Amount
        </div>
        <div class="slds-var-m-around_medium">
            <lightning-combobox
                label="Opportunity Stage"
                options={stageOptions}
                onchange={handleStageChange}
                value={searchStage}>
            </lightning-combobox>
            <lightning-input
                type="number"
                onchange={handleAmountChange}
                class="slds-var-m-bottom_small"
                label="Amount"
                placeholder="Enter Amount to Search Opps..."
                value={searchAmount}>
            </lightning-input>
            <template lwc:if={hasOpps}>
                <table
                    class="slds-table slds-table_bordered slds-table_cell-buffer">
                    <thead>
                        <tr>
                            <th>Opportunity Name</th>
                            <th>Opportunity Stage</th>
                            <th>Opportunity Amount</th>
                        </tr>
                    </thead>
                    <tbody>
                        <template
                            for:each={opportunities}
                            for:item="opportunity">
                            <tr key={opportunity.Id}>
                                <td>{opportunity.Name}</td>
                                <td>{opportunity.StageName}</td>
                                <td>{opportunity.Amount}</td>
                            </tr>
                        </template>
                    </tbody>
                </table>
            </template>
            <template lwc:elseif={error}>{error.body.message} </template>
            <template lwc:else> No matching Data Found.... </template>
            <br />
            <lightning-button
                label="Get Opportunities"
                variant="brand"
                onclick={fetchData}>
            </lightning-button>
        </div>
    </lightning-card>
</template>

apexImperativeMethodWithParams.js


// Import the LightningElement class and the wire decorator from the lwc module
import { LightningElement } from "lwc";
import getOpportunities from "@salesforce/apex/OpportunityController.getOpportunities";
 
export default class ApexImperativeMethodWithApexImperativeMethodWithParamsComplexParams extends LightningElement {
  // Defining class properties
  opportunities;
  error;
  searchAmount = "2500"; //set default search amount
  searchStage = "Prospecting"; //set default search amount
  // Defining the stage options as an array of objects
  stageOptions = [
    { label: "---None---", value: "" },
    { label: "Prospecting", value: "Prospecting" },
    { label: "Qualification", value: "Qualification" },
    { label: "Value Proposition", value: "Value Proposition" },
    { label: "Closed Won", value: "Closed Won" },
    { label: "Closed Lost", value: "Closed Lost" }
  ];
  /**
    * connectedCallback method is used to get the matching
      opportunity data on page load for searchAmount = "2500"
      and searchStage = 'Prospecting'
    */
  connectedCallback() {
    this.fetchData();
  }
 
  /**
   * handleAmountChange method is used to handle the change of amount field
   * It assigns the searchAmount property to user entered opportunity amount value.
   * @param {Object} event - event data
   */
  handleAmountChange(event) {
    this.searchAmount = event.target.value;
  }
  /**
   * handleStageChange method is used to handle the change event of stage field.
   * It assigns the searchStage property to user selected opportunity stage value.
   * @param {Object} event - event data
   */
  handleStageChange(event) {
    this.searchStage = event.detail.value;
  }
  /**
   * fetchData method is used to handle the data returned from the getOpportunities Apex method.
   * It convert the searchStage & searchStage to an parameterObject
     and shows us an example of passing multiple params to apex
   * It assigns the data to the opportunities property.
   * It also assigns the error to error property in case of error.
   * @param {Object} event - event data
   */
  fetchData() {
    // Creating the object that represents the shape
    // of the Apex wrapper class.
    let parameterObject = {
      someString: this.searchStage,
      someInteger: this.searchAmount
    };   
    /* Calling the imperative Apex method with the JSON
    object as parameter.*/
    getOpportunities({ wrapper: parameterObject })
      .then((result) => {
        this.opportunities = result;
        this.error = undefined;
      })
      .catch((error) => {
        this.opportunities = undefined;
        this.error = error;
      });
  }
  /**
   * get hasOpps method checks whether the opportunities property is not undefined
   * and has at least one opportunity.
   * @returns {boolean} - true if opportunities exist, false otherwise
   */
  get hasOpps() {
    return this.opportunities && this.opportunities.length > 0;
  }
}

apexImperativeMethodWithParams.js-meta.xml


<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>56.0</apiVersion>
     <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
        <target>lightning__Tab</target>
    </targets>
</LightningComponentBundle>

OpportunityController.cls



 * This class is used as an controller class for apexImperativeMethodWithParams LWC.
 */
public with sharing class OpportunityController {
    /**************************************************************************************
    * @Description : The getOpportunities returns a list of Opportunity objects,
    *                based on the user selected stage and amount.
    * @Param       : CustomWrapper - wrapper object
    * @Return      : List<Opportunity> opportunities
    **************************************************************************************/
    @AuraEnabled
    public static List<Opportunity> getOpportunities(CustomWrapper wrapper) {
        List<Opportunity> opportunities = new List<Opportunity>();
        if(String.isNotEmpty(wrapper.someString) && String.isNotEmpty(wrapper.someInteger)){
            Integer amt = Integer.valueOf(wrapper.someInteger);
            opportunities = [SELECT Id, Name, StageName, Amount, CloseDate
                                    FROM Opportunity
                                    WHERE StageName=: wrapper.someString
                                    AND Amount >= :amt
                                    WITH SECURITY_ENFORCED];
        }       
        return opportunities;
    }
    /**
     * Wrapper class used to demonstrate how we can pass complex paramters from LWC.
     * Note that inner classes are not supported when exchanging with LWC.
     */
    public with sharing class CustomWrapper {
        @AuraEnabled
        public string someInteger { get; set; }
        @AuraEnabled
        public String someString { get; set; }
    }
}