Salesforce Lightning Web Components Developer Guide: Section 1

We’re kicking off the Lightning Web Components developer guide, a series focused on LWC (Lightning Web Components). Throughout this series, we’ll delve into the structure and development principles of LWC components, cover markup basics, explore inter-component communication, and connect components with Apex.

Let’s embark on our journey into Lightning Web Components, commonly known as LWC. LWC stands as a user-friendly framework aligned with modern web programming standards, tailored for crafting web components compatible with Salesforce. Today’s agenda covers LWC fundamentals, encompassing its definition, structure, theoretical aspects, Salesforce’s recommended development practices, best practices, and more. Let’s dive in!

What is LWC?

Lightning Web Components is an easy-to-use framework. Its programming model leverages modern web standards such as web components, custom elements, Shadow DOM, and ECMAScript modules to create reusable user interface components. LWC is supported by Salesforce, which means you can create, test, and deploy LWC components directly on the Salesforce platform.

LWC uses a component-based architecture consisting of HTML, CSS, and JavaScript files. LWC also has a hierarchical structure that allows components to be composed of other components. This enables developers to create complex user interfaces by combining simple, reusable components.

In LWC, each component is defined as a JavaScript class that extends the LightningElement base class. This class contains properties, methods, and lifecycle hooks that define the component’s behaviour.

LWC provides a set of fundamental tools to help you manage the state and lifecycle of your components. Some of the key tools include:

  • ConnectedCallback() is a lifecycle hook that is invoked when the component is inserted into the DOM. This means the component has completed rendering and is ready to be displayed. You can use this hook to perform any tasks that require access to the DOM, such as setting up event listeners or fetching data from the server.
connectedCallback() { 
     // Add a window resize event listener 
     window.addEventListener('resize', this.handleWindowResize); 
} 
handleWindowResize(event) { 
     // Your logic for handling the window resize event goes here 
     // You can access event properties like event.target, 
     event.clientX, event.clientY, etc. 
     // For example, you can update component attributes or call 
     methods. console.log('Window resized:', event.target.innerWidth, event.target.innerHeight); 
}

  • DisconnectedCallback() is another lifecycle hook that is invoked when the component is removed from the DOM. This means the component is no longer visible and is being destroyed. You can use this hook to perform any cleanup tasks, such as removing event listeners or canceling any pending requests waiting for processing.

disconnectedCallback() { 
     // Remove the window resize event listener when the component is disconnected 
     window.removeEventListener('resize', this.handleWindowResize); 
}

RenderedCallback() is a lifecycle hook that is invoked after each rendering cycle of the component. This means it is invoked every time the component is displayed, whether it’s the first time or next times. You can use this hook to perform tasks that require access to the rendered DOM, such as manipulating the DOM or updating the component’s state. In the example, you can see that renderedCallback() adds a style class to the button element every time the component is displayed. This allows you to modify the DOM after it has been rendered, which can be useful in certain scenarios. It’s important to note that renderedCallback() can be called multiple times during the component’s lifecycle, so it’s important to be careful to not to perform lengthy or resource-intensive operations or make too many API calls in this hook.

@api buttonStyle; renderedCallback() { 
     // When we finished Render we adding some spec ific style 
     // class to our button which was passed from parrent LWC 
     const button = this.template.querySelector('button'); 
     button.classList.add(this.buttonStyle); 
}

  • errorCallback() is a lifecycle hook that is invoked in case of an error during the rendering of a component in LWC. It provides the opportunity to handle and catch the error, and take necessary actions to restore the component’s state or display an appropriate error message. This hook is invoked after an error occurs and allows for additional actions such as logging the error, notifying the user, or other DOM manipulations. By using errorCallback(), you can control how the component displays and responds to errors, helping to ensure a more stable and reliable operation of your LWC component.

errorCallback(error, stack) { 
     // We catch errors in error callback and can work with them as we need 
     console.error('Error message:', error); 
     console.error('Error stack trace:', stack); 
     this.doSomething(); 
} 
doSomething() { 
     // Perform some actions 
}

Using Variables in LWC

Now let’s discuss the basics of using variables in LWC. In LWC, there are two main levels of variables: class-level variables and function-level variables.

Class-level variables are declared outside of any functions in the JavaScript class of the component and are accessible to all functions within the class. They are typically used for storing data that needs to be accessed from the HTML or for holding data that will be manipulated throughout the component’s lifecycle.For example, you can declare a class-level variable named “count” that tracks the number of button clicks:

count = 0; 
maxLimit = 10; 
handleClick() { 
     if (this.count < this.maxLimit) { 
          this.count++; 
     } 
}

In this example, the variable “count” is declared outside of the “handleClick” function, allowing the function to access and update it by referencing it through “this.” Each time the “handleClick” function is called (such as when a button is clicked) the counter variable is incremented. Additionally, it checks whether we have reached the specified limit before incrementing the count.

On the other hand, function-level variables are declared inside a function and are only accessible within that function. They are typically used for storing temporary or intermediate data that is needed only within the context of that specific function. For example, you can declare a function-level variable named “isValid” that checks the validity of user-entered code.

In this example, the “isValid” variable is declared inside the “handleInputChange” function and is used to validate user-input data. The function-level variable is necessary here to perform the validation only once and then use the result for both the IF condition and error status.

showError = false; handleInputChange(event) { 
     let userInput = event.target.value; 
     let isValueInvalid = userInput.length > 10; 
     if (isValueInvalid ) { 
          console.log('You hit limit'); 
     } 
if (userInput.length === 0 || isValueInvalid) { 
     this.showError = true; 
     } 
}

Annotations in LWC

An integral part of working with LWC is using various annotations. Annotations are essential elements in LWC development in Salesforce. They are used to add metadata to the classes and methods of components, which helps ensure their proper functioning.

In this article, we will introduce just one of them – @track. The following annotations, such as @api and @wire, will be covered in future LWC articles.

@track: This annotation is used to track changes in the properties of a component. It allows for automatic updates to the component’s markup when property values change. It’s important to understand that you don’t need to use the @track annotation for simple data types like strings, numbers, and booleans, as they are already automatically tracked by LWC. The @track annotation is only necessary for tracking changes in objects and arrays because changes in these data types are not automatically tracked.

In this example, we are adding elements to an array, and to ensure that our component can correctly track changes in it, we have added the @track annotation.

A few tips for using LWC Effectively

  • Keep your components simple, use a modular structure, break components into smaller reusable parts, and combine them to create more complex interfaces.
  • A good practice is to have components that perform just one function but do it well. Avoid creating monolithic components that do too much.
  • Use design patterns such as PubSub, Decorator, and Facade to organize your code and improve code reusability.
  • Use the Salesforce Lightning Design System (SLDS) to ensure that your components align with the Salesforce user experience and are consistent with other Salesforce components.
  • Write modular tests for your components to ensure they behave correctly and catch errors early in the development cycle.

Creating the first LWC component

And now, let’s start creating our first LWC component, and apply in practice everything discussed above. For this, we will develop a simple form for adding simple numbers.

<template> 
<section class="main"> 
<h1>Number sum form</h1> 
<div class="twoColumnStyle"> 
<fieldset> 
<div class="threeColumnStyle"> 
<lightning-input value={firstNumber} 
label="First Number" onchange={handleInput} 
data-name="firstNumber"> 
</lightning-input> 
<lightning-input value={secondNumber} 
label="Second Number" onchange={handleInput} 
data-name="secondNumber"> 
</lightning-input> 
<lightning-button label="Sum" 
onclick={handleButtonClick}> 
</lightning-button> 
</div> 
<div class="oneColumnStyle"> 
<lightning-input value={result} 
label="Result"> 
</lightning-input> 
</div> 
</fieldset> 
<section> 
<template lwc:if={isHistoryExist}> 
<h2>History of operations</h2> 
<template for:each={operationsHistory} 
for:item="historyRecord"> 
<div key={historyRecord.value} class="borderStyle"> 
{historyRecord.firstNumber} + {historyRecord.secondNumber} = {historyRecord.value} 
</div> 
</template> 
</template> 
</section> 
</div> 
</section> 
</template>

First of all, let’s create the markup for our component.

There we’ll be able to see 2 input fields for entering numbers, a button to initiate the calculation, a field to display the calculation results, and a block for the history of operations that have already occurred.

The history block iterates over an array of objects where we store the necessary data (more details on iteration in LWC will be discussed in future LWC articles).

You can also notice the LWC:IF directive, which won’t display this block if the history is empty.

One important thing to note is adding of a “data-name” attribute for the input fields, which will allow to access them later in JS to obtain data.

import { LightningElement, track } from 'lwc'; 
export default class SimpleNumberSumForm extends 
LightningElement { 
     @track operationsHistory = []; 
     firstNumber; secondNumber; 
     result; connectedCallback() { 
          // One of common uses of connectedCallback is calling Apex method to retrieve data from SF
         // We will show examples of this usage in a future video 
} 
errorCallback(error, stack) { 
     console.log(error, stack); 
} 
handleInput(event) { 
     this[event.target.dataset.name] = event.target.value; 
} 
handleButtonClick() { 
     this.result = parseInt(this.firstNumber) + parseInt(this.secondNumber); 
     if (!Number.isNaN(this.result)) { 
          let history = JSON.parse(JSON.stringify(this.operationsHistory)); 
          history.push({firstNumber: this.firstNumber, secondNumber: this.secondNumber, value: this.result}); 
          this.operationsHistory = history; 
     } 
} 
get isHistoryExist() { 
     return this.operationsHistory.length >= 1; 
     } 
}

Let’s proceed to our JS class. Initially, we’ve imported the @track annotation. We’ve used this annotation at the class level to establish an array, ensuring our LWC component reacts to alterations in the array. This enables iterative functionality to consider the latest changes within it.

Following this, we’ve defined two variables: one for capturing user input and another to store calculation results.The subsequent step involves the connectedCallback. In this component, we can leverage this hook to initialize the history of previous operations from Salesforce using Apex. However, we won’t delve into this presently. Subsequent articles in the LWC series will explore various methods of LWC communication with Apex, illustrating this scenario as an example.Additionally, we’ve included an errorCallback to present errors and their respective stack traces in the console.

We also added two functions: the first, handleInput, is invoked when the user enters or changes data in the input field. In this function, we get an event that contains the value entered by the user and the name of the field that sent this event. Since we previously added “data-name” tags to our input fields in HTML, this construction will allow us to use one function to assign data to all our inputs because we assigned the same name as in the class-level variable that stores this data. This dynamic access can also be replaced with the standard “this.” construction and the variable we are accessing.

Next, we have a function that triggers when the button is pressed. It takes the first and second numbers, adds them, and stores the result. Then, if this number is valid, we add it to the history of operations, which will be displayed on the page.

Finally, we see a getter that checks whether the length of the operation history array is not empty for our LWC:IF condition, which will display the history.

Lightning Web Components Developer Guide

Lightning Web Components (LWC) represents a contemporary, standardized programming model designed for constructing diverse user interface components that seamlessly function within Salesforce. By adhering to Salesforce’s guidelines and best practices, one can craft high-caliber, scalable, secure, and effortlessly maintainable components. Leveraging robust underlying tools, LWC simplifies component state and lifecycle management. Its modular architecture facilitates the creation of intricate interfaces by amalgamating several straightforward components.

In our upcoming article, we’ll delve into the fundamental markup principles within LWC. This exploration will unveil the capabilities and functionalities offered by LWC for building our Salesforce applications.