The Front End Experience: Salesforce Lightning Web Components

Bobby Gallahue
Cervello, a Kearney Company
16 min readApr 23, 2021

Salesforce released the Lightning Web Components framework in Spring 19' in an effort to more closely align their proprietary framework with Web Component Standards. It was a large iteration and introduced a significant amount of new functionality in addition to revamping some existing Aura functionality. Specifically options for interacting with the Salesforce database expanded, @wire was introduced and there were large improvements to Lightning Data Services. However, as any with a Netflix account can attest to, the paradox of choice can be debilitating, and knowing when and how to use each methodology can be confusing. Hopefully we can provide some guidance and clarity in this article.

Retrieving data from the server (i.e., anything saved on Salesforce) in Lightning Web Components is centered on three main methodologies: Lightning Data Services (LDS), Wired Apex, and Imperative Apex. In broad terms, LDS is the least flexible (i.e. the most restrictive in where it can be utilized) but by far the simplest to set up and maintain; Wired Apex has a specific set of built in functionality that is perfect in some situations but detrimental in others; and Imperative Apex provides the most flexibility but requires the highest level of effort, most functionality needs to be built by the developer.

The baseline thought process when building a component and choosing a methodology should be use LDS whenever possible and, if not possible, use Imperative Apex unless you have a specific use case that can take advantage of Wired Apex.

Utilizing Lightning Data Services

Advantages

Use Lightning Data Services whenever possible to retrieve record data, metadata or schema. Here are three reasons to turn to LDS first rather than Apex:

1. It’s more performant and faster than retrieving data through Apex.

2. It’s quicker to write and maintain (no test classes).

3. It will combine similar database calls and automatically push updated data to the component. (This is discussed in more detail in the LDS and the Cache section below.)

Disadvantages

The main disadvantage is that LDS is that it is limited to scenarios where you only need to pull in one record at a time. (There is a way to use list views to pull in lists of records, but in large organizations, this is not advisable.) You also can’t select a record based on a WHERE clause like in a SOQL query; you must have a record ID. When you need to retrieve a list of records, save a list of records, or delete a list of records, you will want to move onto Apex. But if you’re creating, updating, or deleting records one at a time, or better yet, just working with a single record, LDS is the best option.

How to Retrieve Metadata and Data with LDS

Two main modules can be used in our component to retrieve metadata or data. The links below have a substantial amount of information on the syntax for using each one:

· lightning/uiObjectInfoApi retrieves object metadata (fields, child relationships, and record types) and detailed picklist value data. The picklist data is particularly useful because you can get controlling field data. For example, you can accurately show available picklist values for picklist field B based on what a user selects in picklist field A based on how it’s set up in Object Manager.

· lightning/uiRecordApi can be used to retrieve records, updates records, create records, or delete records.

LDS and the Cache

The third advantage we listed above needs some further explanation. The official documentation states the following:

What this is saying is that if you have two separate components on the same page that are both using LDS and calling the same record, it will not load the same record twice but rather load it once into the LDS cache where both records will have access to it. This is especially relevant on Salesforce Community pages, where there are advantages to keeping components separate (most notably access to the design file to modify properties). But in general, loading the same record data twice or more isn’t best practice. This eliminates the issue if LDS can be used.

The second part we want to expand on is the LDS automatically pushing updated data to the component.

The official documentation states the following:

This infographic from this documentation displays how this works best:

However, note that this automatic updating only occurs when the components are on the same page and belong to the same user session, as stated below:

So if another user updates a record that your component is displaying (using LDS), the record will not be automatically refreshed. This certainly limits the use cases of this functionality, but where applicable, this can certainly save a lot of lines of code.

Situations not clearly documented in Salesforce official documentation

1. Sometimes, you might want to retrieve relationship fields. This is possible with LDS; you just need to correctly import fields in the schema. It might look something like this:

IMPORT INCIDENT_REPORT_NAME_FIELD from “@salesforce/schema/Claims__c.Incident_Report_Case__r.Name”

2. Reactive properties are essential when using LDS since we don’t control when the wire runs. (We will get into when the wire actually runs in the Apex section.) It is outside of the life-cycle hooks (i.e., connectedCallback), so you need the wire to be able to react when the variables (such as recordId) are finally set or are modified. To make a property reactive, we just use the syntax ‘$PROPERTY’ instead of this.PROPERTY.

Example:

export default class TestClass extendsLightningElement {

@api recordId;

@wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD, INDUSTRY_FIELD] })

account;

}

Conclusion: when to definitely use LDS

To retrieve, update, insert, or delete a single record (using getRecord, updateRecord, createRecord, deleteRecord respectively from the uiRecordApi) when you have record context, i.e., we can pull in @api recordId from the page the LWC is placed on.

To retrieve metadata about an object using the uiObjectInfoAPI, there is no need to go to Apex for this information. This wire adaptor is comprehensive and will cache the data on the client side.

Apex Methods

When you have complex data retrievals or when using LDS isn’t possible, you can move over to retrieving data with Apex. There are two ways to do this: using a wired Apex method and calling the Apex method imperatively. Note that LDS uses the wire as well; what we are discussing here is specifically Wired Apex methods, which expand the amount of data we can retrieve.

This Salesforce article gives a more detailed explanation of the syntax for how to use each. We won’t go into detail about the syntax here since the official Salesforce documentation does a good job of that. Instead, we’ll discuss how some of this functionality behaves in the real world.

Utilizing @Wired Apex Methods

Advantages

· It allows you to write significantly fewer lines of code in certain use cases.

· You can set up a reactive cached database retrieval in just a few minutes.

Disadvantages

· It can be tricky to refresh the Wire when a direct parameter is not modified (i.e. a record is saved, so you know the cache is now stale)

· You have to make any Apex method cacheable to use it with the wire, which makes it challenging to reuse existing Apex code.

· Data is immutable. If you set it to a property (instead of a function), that function can’t be modified.

Using the wire

The wire can be used in certain situations to write significantly fewer lines of code, such as when the parameters of our methods are being frequently updated and we need to make a significant amount of calls to the database. Understanding exactly when the wire runs and how the cache works allows you to accurately decide if it fits your use case.

For discussion purposes, let’s set up a simple data retrieval of a contact using the wire.

@Wire

JavaScript

Apex

When the wire is called

The wire will run whenever any of the reactive parameters change. For example, in the code above, if the recordId property changes, the wire will provision data. A more complex and unclear question is when does the wire initially run? It’s not initiated by any of the life-cycle events (such as connectedCallback) like an Imperative Apex method might be. The documentation states the following:

So after the component is created (green dot in the life-cycle event chart), the wire “runs” initially, which means it provisions the object or functions data/error values with undefined.

We can confirm the initial provisioning behavior timeline by changing the recordId property, which is being defined through page context, i.e., the component being placed on a record page to this recordId.

This immediately causes the below error:

The error is interesting because it says “can’t read the “recordId” property of undefined”. What causes the error is that “this” is not yet defined. The Constructors Super statement is what creates “this”, and since the wire runs before the constructor, we can’t use “this” without causing an error:

The scenario below helps confirm when the wire fills in actual data. We set up a simple retrieval of contact records. (There are only two in the Dev Org, so there shouldn’t be any delays attributed to a large or complex retrieval.) We also remove any parameters from the wire so we can confirm that it is not waiting or reacting to any parameters being filled in. Then we set up a series of simple console.logs in each of the life-cycle events.

And you can see that contactList, which the wire defines with its retrieved values, is filled in in none of the lifecycle events:

This shows that we cannot interact with the retrieved data or metadata in any of the life-cycle events since they will not be defined. We can only do so in the function (getWiredContacts) called by the @wire. Yet this does allow us to modify any properties that are parameters of the wire within life-cycle events without issue; and the initial data retrieval will return data, taking our modifications into consideration. (This was tested, and the wire does not run twice.)

How to manually refresh the wire

We’ve stated that the wire runs when parameters change, and we’ve shown when it initially runs. But sometimes, you might need to force the Wired Apex to run outside of those two contexts. For instance, updating or inserting a record might produce the need to retrieve that updated or inserted record even though no parameters change (for example, we’re retrieving contacts with the first name of Jill, and we just inserted another Jill in the database). The wire will not automatically retrieve that updated record, so we need to force it to run again. We can do so with refreshApex. As the example below lays out, we’re setting the retrieved object (in this case, the variable “value” is the retrieved object) to a property “wiredActivities” and then using that in our refreshApex call, refreshApex(this.wiredActivities). Although we can force a manual refresh, having to do so indicates that we should have been using an imperative Apex method from the start.

Some might get slightly confused by the const {data, error} = value, but all that is doing is pulling out the data and error properties from the value object into const variables called data and error. (This syntax was introduced in ES6.) The below does a better job explaining this. It doesn’t have anything to do with the refreshApex call.

The Cache

The second thing we want to discuss is the cacheable=true addition to the @auraenabled tag on the Wired Apex method. This is a requirement for Apex methods called by the wire. (It can be added to Imperative Apex methods, but it is optional.) The cache is designed to store results returned from the server, with the purpose being if the wire is called without the parameters changing (such as “$recordId”), then it will use the cached results. However, other than what is documented by Salesforce below, there isn’t much information on how the cache actually works.

So in order to dive a little deeper, we will do some real-world testing with a basic search component (very original).

HTML

JavaScript

Apex

For this very simple component, the user types in the input. It is handled by the handleChange method. It modifies the searchInput property, which in turn causes the wire to fire and retrieve data. Data is set to the searchOutput property, which is referenced in our HTML file.

Our test org has three contacts:

When we type “Jill” into the Lightning-Input, the wire returns three contacts, each of which has a first name of Jill:

Through the console.logs we set up, we can see that each time we type a letter, our lightning input handler method sets searchInput to the new user-typed value, and the wire sends a server call:

We can confirm there were four SOQL queries or server calls ran — one for each letter typed:

2.

3.

4.

Now this is where it gets interesting: if we type our search term “jill” as fast as we can, we can see that our change handler is fired four times, but the wire is only returned once:

This might indicate that the wire holds the server call for a few milliseconds to see if any of the wire method parameters get modified again, but we still have the same result on the server. (I didn’t include screenshots, so you’ll just have to trust me. Four queries are run. It looks like the wire consolidates the return if the server calls are made in quick succession to mitigate having to process the data four times. So it’s not necessarily the cache at work, but it’s interesting behavior.

Our next test will be to first search for “jill” — returning our three contacts — and then insert another contact record (in a separate window) with jill as the first name.

First, we type “jill”:

Then we insert a new contact in another tab:

With the initial search term not modified (i.e., we haven’t touched anything, and it’s still “jill”), the wire will not automatically refresh the data. So it still shows three contacts even though there are four on the server that fit our current search parameters.

When we modify the search term from “jill” to “jill ” (just adding a white space), the wire detects that as a modified parameter, makes a database call, and returns four contacts:

However, when we backspace, it shows only three contacts since it is now retrieving data from the cache:

When we delete another letter, it still shows three contacts since it’s still using cached data and not making a database call:

So it’s best to think of the cache as an object with the parameters being the keys and the returned data being the values. (In the values below, I just used the post-processed data, not recordIds, to make it easier to read.)

Cache = {

“j” : “Jill Cleaner, Jill Homeowner, Jill Decorator”,

“ji” : “Jill Cleaner, Jill Homeowner, Jill Decorator”,

“jil” : “Jill Cleaner, Jill Homeowner, Jill Decorator”,

“jill” : “Jill Cleaner, Jill Homeowner, Jill Decorator”

“jill ” : “Jill Cleaner, Jill Homeowner, Jill Decorator, Jill Smith”

}

After about five or six minutes, the cache refreshes, I believe it has a built in timer. So after that time period, when I did the same steps above but added and subtracted a white space, the search term “Jill” was refreshed and returned four contacts:

The cache provides a big advantage in situations where the user has direct access to the server or database and can potentially make thousands of server calls. Also remember that the cache is required for wired methods but can be used with Imperative Apex methods. A key disadvantage would be that if you want to use the Apex method as uncached in another component, there doesn’t appear to be a way to toggle this behavior on and off.

Utilizing Imperative Apex Methods

Advantages

· It’s simpler than the wire and makes a database call when you want it to.

· It has more flexibility and can ramp up in functionality when the scope of the component expands.

Disadvantages

· It has less functionality baked in, but we’re manually building and writing additional functionality.

When it comes to Imperative Apex, the keyword is flexibility. We have much more control over when Imperative methods run and when they refresh. Therefore, we can facilitate more complex data retrievals and avoid struggling with functionality (like the wire and cache) where we don’t have direct access to modify its behavior.

For discussion purposes, below is a basic component example using an Imperative Apex call.

JavaScript

Apex

When Imperative Apex is called

This is going to be a relatively short section since the answer is “whenever you want.” Typically, if the data is essential for the component, then it is initially called in the connectedCallback (but could be placed in any life-cycle event). We can easily refresh the data by just calling the method containing the Imperative Apex in any part of our component, and it will perform a clean database retrieval.

Promises

One of the key differences between the wire and calling an Apex method imperatively is that an Imperative Apex method returns a promise while the wire returns an object. (Well, a promise is technically an object, but you get the point.) Promises offer more advanced functionality that we can use to handle some of the complex scenarios we encounter. This article does an excellent job detailing some of the functionality of static promise methods that we can apply to various use cases.

A real-life example might be if we have two Apex methods that both retrieve data that needs to be displayed together at the same time. We could handle that Apex side, creating a wrapper object and combining the data from the methods there. But many times, we want to use existing methods or avoid creating specialized Apex just for our component. We can use Promise.All to make sure our data from the two server calls comes in at the same time and is processed at the same time. Promise.All won’t go to the .then statement until both async operations return. Then we can efficiently process both. The example below shows retrieving two different sets of contacts and then processing them at the same time. The data is set to contactList once instead of two separate times. It is much simpler and shows just a small fraction of the flexibility advantage imperative that Apex offers.

Apex Method Conclusions

When do we want to use the wired method?

The wire provides some great shortcuts, but consider using it only in situations where we have parameters that are being frequently modified, whether by the user or another component. We can cache results in both Imperative and Wired Apex, so that should not be the deciding factor in which one you use. The cache can also be unnecessary for most situations but is required for wired methods.

When do we want to use Imperative methods?

For most situations when we are loading and displaying a largely static set of data from the server that will only be updated maybe once or twice during a user session (most components), it’s simpler to use Imperative Apex methods. We have a lot more flexibility, and if the scope of what we’re building increases, we can expand the functionality of Imperative Apex to facilitate that.

About Cervello, a Kearney company

Cervello, a Kearney company is a data and analytics consulting firm and part of Kearney, a leading management consulting firm. We help our leading clients win by offering unique expertise in data and analytics, and in the challenges associated with connecting data. We focus on performance management, customer and supplier relationships, and data monetization and products, serving functions from sales to finance. We are a Salesforce partner and help our clients implement, customize, and optimize the platform into the best solution for their needs.

--

--