top of page
Blog
  • Writer's pictureArtur Romaniuk

Lightning Web Components Developer Guide: Chapter 1

We are starting the Lightning Web Components developer guide, a series of articles dedicated to LWC (Lightning Web Components). We will understand the structure and development principles of LWC components, the basics of markup, communication between components, and the connection of components with Apex.


So, let's begin our introduction to Lightning Web Components, or LWC. LWC is a user-friendly framework based on modern web programming standards designed for creating web components that work within Salesforce. Today we will cover the fundamentals of LWC, including what it is, its structure, theory, Salesforce recommendations for component development, best practices for using LWC, and more. Let's get started!


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;
    }
}

Now, let's move on to our JS class. First, we imported the @track annotation. We declared a class-level array with this annotation to store the history. In this case, the annotation is necessary for our LWC component to react to changes in the array and allow for iteration to work considering the latest changes in it.

Next, we declared two variables to store user input and a variable to store calculation results.

The next is connectedCallback. For example, in this component, we can use this hook to initialize the history of previous operations from Salesforce using Apex. We won't focus on this now because in future articles in the LWC series, we will explore various ways of LWC communication with Apex and demonstrate this case as an example.

Next, we added an errorCallback to display errors and their stack trace 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


So, Lightning Web Components is a modern, standard programming model for creating multiple user interface components that work natively in Salesforce. By following Salesforce's recommendations and best practices, you can create high-quality, scalable, secure, and easily maintainable components. With its powerful underlying tools, LWC makes it easy to manage the state and lifecycle of components, and its modular architecture allows you to create complex interfaces by combining simple, multiple components.


In the next article, we will discuss the basic principles of markup in LWC, exploring the capabilities and features that LWC provides for developing our Salesforce applications.


 

Alexey Nayda, CEO at Sparkybit
Alexey Nayda, CEO at Sparkybit


Salesforce is our superpower


Be my guest to talk about your challenges and goals on a free Salesforce Advisory Demo.





bottom of page