LWC in Salesforce

·

14 min read

Lifecycle Hook in LWC

A lifecycle hook is a callback method that triggers at a specific phase of a component instance’s lifecycle. With LWC, you can use the following lifecycle hooks.

Here’s a brief overview of some essential lifecycle hooks:

  1. constructor(): This is where the component is initialized. You can set default values and perform one-time setup.

  2. connectedCallback(): After the component is added to the DOM, this hook runs. It’s a great place to perform DOM manipulations and data retrieval.

  3. render():

    • Allows you to override the default rendering behavior by specifying a custom template.

    • Rarely used unless you want to dynamically select a template.

    • The main use case is to conditionally render a template. Define business logic to decide which template (HTML file) to use. The method must return a valid HTML template.

  4. renderedCallback(): Called after the component’s DOM is updated and rendered. This hook is triggered after rendering occurs. It’s ideal for operations that require knowledge of the rendered DOM.

  5. disconnectedCallback(): When the component is removed from the DOM, this hook is invoked. Use it for cleanup operations and resource releases.

  6. errorCallback(): If an error occurs during rendering, this hook is called. It’s your opportunity to gracefully handle errors.

The render() method is not technically a lifecycle hook. It is a protected method on the LightningElement class.

Shows the lifecycle flow of a component instance from creation through renderedCallback.


Decorators in LWC

  1. @api decorator

    • The @api decorator in Lightning Web Components (LWC) is used to expose public properties and methods to other components.

    • This makes them accessible outside of the component, typically by a parent component.

    • It is the way to define what a component's interface should be, i.e., which properties and methods should be accessible and usable by other components.

  2. @track decorator

    • The @track decorator in Lightning Web Components (LWC) is used to make properties reactive, meaning that when the value of the property changes, the component will automatically re-render the UI to reflect the updated value.

NOTE: If you want a property to be accessible by a parent component, you should use @api, not @track. If you only want the property to be reactive within the component itself but not exposed to parents, use @track without @api.

  1. @wire decorator

    • In Lightning Web Components (LWC), the @wire decorator is used to read data from Salesforce and other external services asynchronously.

    • It binds a property or function to a service call and automatically handles the lifecycle of the call. When data is returned or changes, the component automatically re-renders.


Lightning Data Service

Lightning Data Service is a centralized data caching mechanism which is utilized to perform create, read, update or delete on a record without having a server side apex call in Lightning web components.

Records loaded in Lightning Data Service are cached and shared across components. If a page is composed of components showing the same record, all components show the same version of the record. Components accessing the same record see significant performance improvements, because a record is loaded once, no matter how many components are using it.

In a Lightning web component, perform operations on data and access metadata using these technologies built on Lightning Data Service.

  • Base Components:lightning-record-edit-form, lightning-record-form, and lightning-record-view-form

  • The base component of Lightning Data Service are

    • lightning-record-form: A form with standard lightning UI to create, view or edit a record

    • lightning-record-edit-form: A form to create record with specified fields or update fields in an existing record

    • lightning-record-view-form: A form to display specific fields data of a record in read-only mode

Advantages of Lightning Data Service

  • No server side apex call as the record is cached & retrieved from client-side cache of record data that has been loaded via a wire adapter, thus eliminating multiple calls to the server

  • Lightning Data Service is built on top of User Interface API, the responses respect CRUD access, field-level security, and sharing settings pertaining to a User.


Lightning Record Edit Form

  • Edit Record

  • If the record-id attribute is provided, the form is used to edit the specified recor

<lightning-record-edit-form
  object-api-name="Account"
  record-id="001XXXXXXXXXXXXXXX" <!-- Replace with the actual record ID -->
  onsuccess={handleSuccess}
>
  <lightning-input-field field-name="Name"></lightning-input-field>
  <lightning-input-field field-name="Phone"></lightning-input-field>
  <lightning-button type="submit" label="Save"></lightning-button>
</lightning-record-edit-form>
  • Create Record

    • If the record-id attribute is omitted, the form switches to create mode, allowing you to insert a new record.
<lightning-record-edit-form
  object-api-name="Contact"
  onsuccess={handleSuccess}
>
  <lightning-input-field field-name="FirstName"></lightning-input-field>
  <lightning-input-field field-name="LastName"></lightning-input-field>
  <lightning-input-field field-name="Email"></lightning-input-field>
  <lightning-button type="submit" label="Create"></lightning-button>
</lightning-record-edit-form>

Lightning Record View Form

<template>
  <lightning-card title="Account Details" icon-name="standard:account">
    <div class="slds-p-around_medium">
      <lightning-record-view-form 
        object-api-name="Account" 
        record-id={recordId}>

        <!-- Fields to display -->
        <lightning-output-field field-name="Name"></lightning-output-field>
        <lightning-output-field field-name="Phone"></lightning-output-field>
        <lightning-output-field field-name="Website"></lightning-output-field>
        <lightning-output-field field-name="Industry"></lightning-output-field>

      </lightning-record-view-form>
    </div>
  </lightning-card>
</template>
import { LightningElement, api } from 'lwc';

export default class AccountViewForm extends LightningElement {
  @api recordId; // Pass the record ID dynamically (e.g., from a parent component)
}

Lightning Record Form

The lightning-record-form component in LWC is a versatile and declarative component that can handle view, edit, and create operations for a Salesforce record. It simplifies development by allowing you to quickly configure a form without writing a lot of boilerplate code. It’s an all-in-one solution compared to lightning-record-edit-form and lightning-record-view-form.


Key Features of lightning-record-form:

  1. Modes: Supports three modes:

    • View: To display a record in read-only mode.

    • Edit: To update an existing record.

    • Create: To create a new record.

  2. Dynamic Fields: Can auto-generate fields using layout-type or define specific fields manually using fields.

  3. Simpler Setup: Automatically handles field rendering, validation, and layout generation based on the Salesforce object metadata.

<template>

    //create mode

    <lightning-record-form 
        mode="create"
        object-api-name={objectApiName}
        fields={fields}
        onsuccess={handleSuccess}
        >
    </lightning-record-form>    


    // edit  mode

    <lightning-record-form
        mode="edit"
        record-id={recordId}
        object-api-name={objectApiName}
        fields={fields}
        onsuccess={handleSuccess}
    >
    </lightning-record-form>

    // view mode

    <lightning-record-form
        mode="readonly"
        record-id={recordId}
        object-api-name={objectApiName}
        fields={fields}
        columns="2"
    >
    </lightning-record-form>

</template>
import { api, LightningElement, track } from 'lwc';
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
import ACCOUNT_NAME from "@salesforce/schema/Account.Name";
import ACCOUNT_INDUSTRY from "@salesforce/schema/Account.Industry";

/*

create 
edit 
view

*/


export default class RecordFormPractice extends LightningElement {

    @api recordId;
    objectApiName = ACCOUNT_OBJECT;

    handleSuccess() {
        alert('Data submitted');
    }

    // create record
    // fields = [ACCOUNT_NAME];


    // edit record
    // fields = [ACCOUNT_NAME, ACCOUNT_INDUSTRY];

    // view record
    // fields = [ACCOUNT_NAME, ACCOUNT_INDUSTRY];

}

Wire adapters

  • In Lightning Web Components (LWC), a wire adapter is a special kind of adapter used to connect an LWC component to Salesforce data or services in a declarative manner.

  • It allows your component to retrieve data from Salesforce without having to manually write imperative JavaScript code for fetching the data.

  • The wire adapter is reactive, meaning it automatically updates the component whenever the data it is wired to changes.

  • In LWC wire adapters are designed to retrieve data declaratively and reactively, but they do not support operations like delete, update, or create directly. These operations require imperative code using Salesforce's Apex methods or UI API.

lightning/uiRecordApi

  • In Lightning Web Components (LWC), the uiRecordApi module provides a set of wire adapters and functions that enable you to work with Salesforce record data. This module allows you to easily retrieve, update, create, or delete record data without having to write complex Apex controllers or custom server-side logic.

  • There are few methods listed below

  1. createRecord(recordInput):

      // Configure your recordInput object with the object and field API names
         const fields = {}
         const recordInput = { apiName: Object API_NAME, fields };
    
         try {
           // Invoke createRecord
           const account = await createRecord(recordInput);
         } catch (error) {
           // Handle error
         }
    

    Example:

     async handleSave() {
             // prepare contact data
             const fields = {
                 AccountId: this.account,
                 FirstName: this.firstName,
                 LastName: this.lastName
             }
             const recordInput = {apiName: 'Contact', fields};
             try {
                 await createRecord(recordInput);
                 this.showToast('Success', 'Contact created successfully', 'success');
             } catch (error) {
                 console.log("error while creating contact ", error);
                 this.showToast('Error', 'Error creating contact ' + error.body.message, 'error')
             }
         }
    
  2. deleteRecord(recordId)

     const recordId = event.target.dataset.recordid;
     try {
         await deleteRecord(recordId);
     } catch (error) {
         // code here
     }
    
  3. updateRecord(recordInput, clientOptions)

     const fields = {
         Id: contactId,
         FirstName: this.firstName,
         LastName: this.lastName
     };
     const recordInput = {fields};
     const updateResult = await updateRecord(recordInput);
    
  4. getRecords

     @wire(getRecords, {recordId:'', fields: []})
     propertyOrFunction
    

refreshApex

  • The parameter you refresh with refreshApex() must be an object that was previously emitted by an Apex @wire.

  • Sometimes, you know that the cache is stale. If the cache is stale, the component needs fresh data. To query the server for updated data and refresh the cache, import and call the refreshApex() function. Invoke refreshApex() only when necessary because it incurs a network trip to query the server.

import { refreshApex } from "@salesforce/apex";
refreshApex(valueProvisionedByApexWireService);

Communication between LWC components

  1. Parent to Child

  2. Child to Parent

  3. Component to Component (Pub/Sub)

Parent to child Communication

  • parentComponent.html

    • In above code we have a child component name as childComponent we need to append c in order to call child component and name is the property which we are sending to child.
<template>
    <lightning-input type="text" value={employeeName} onchange={handleEmployeeName}></lightning-input>
    <c-child-component name={employeeName} onmessage={handleMessage}></c-child-component>
</template>
  • childComponent.html
<template>
    <h2>Hello {name}</h2>
</template>
  • childComponent.js
import { track } from "lwc";
import { api, LightningElement } from 'lwc';

export default class ChildComponent extends LightningElement {
    @api name;
}

Child to Parent Communication

  • Parent.html
<template>
    <c-child-component onmessage={handleMessage}></c-child-component>
    <div>Printing message from child {messageFromChild}</div>
</template>
  • parent.js
import { LightningElement, track } from 'lwc';

export default class ParentComponent extends LightningElement {

    handleMessage(event) {
        // access the data sent via child in parent
        this.messageFromChild = event.detail.data;
    } 
}
  • child.html
<template>
    <h2>Hello {name}</h2>
    <lightning-button onclick={handleClick} label="Click for sending to parent"></lightning-button>
</template>
  • child.js
import { track } from "lwc";
import { api, LightningElement } from 'lwc';

export default class ChildComponent extends LightningElement {
    handleClick() {
        const event = new CustomEvent('message', {
            detail: {data: 'Hello from child'}
        });
        this.dispatchEvent(event);
    }
}

Component to Component (Pub/Sub)

Example using Pub/Sub:

  • Create pubsub/anyname folder under LWC folder. This folder will contain pubsub file which has logic implmented for pubsub.

  • You can access exports of js file by importing them with help of c/filename

  • Also create filename.js-meta.xml and add below code. This xml will contain info related to javascript file.

  • pubsub.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>59.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>
  • pubsub.js

const events = {};

    // subscribe to an event
const subscribe = (eventName, callback) => {
    if (!events[eventName]) {
        events[eventName] = [];
    }
    events[eventName].push(callback);
}

    // publish an event
const  publish = (eventName, payload) => {
    if (events[eventName] && events[eventName].length > 0) {
        events[eventName].forEach((callback) => callback(payload));
    }
}
    // unsubscribe an event 
const  unsubscribe = (eventName, callback) => {
    if (events[eventName] && events[eventName].length > 0) {
        events[eventName] = events[eventName].filter((currentCallback) => currentCallback !== callback);
    }
}

export  {
        subscribe, publish, unsubscribe
}
  • Component1PubSub.html
<template>
    <lightning-card>
        <lightning-button label="Publish Event" onclick={handlePublishEvent}></lightning-button>
    </lightning-card>
</template>
  • Component1PubSub.js
import { LightningElement } from 'lwc';
import {publish} from 'c/pubsub';

export default class Component1PubSub extends LightningElement {
    handlePublishEvent() {
        const payload = {data: 'Hello world'};
        publish('messageEvent', payload);
    }
}
  • Component2PubSub.html
<template>
    <lightning-card>
        <div>
            Message from component 1 {message}
        </div>
    </lightning-card>    
</template>
  • Component2PubSub.js
import { LightningElement, track } from 'lwc';
import {subscribe, unsubscribe} from 'c/pubsub';

export default class Component2PubSub extends LightningElement {
    @track message = '';

    connectedCallback() {
        subscribe('messageEvent', (payload) => this.handleMessageEvent(payload));
    }

    disconnectedCallback() {
        unsubscribe('messageEvent', (payload) => this.handleMessageEvent(payload));
    }

    handleMessageEvent(payload) {
        console.log("Payload messgae", payload.data);
        this.message = payload.data;
    }
}

Lightning Messaging Service (LMS):

  • LMS stands for Lightning Message Service. It's a versatile communication framework within Salesforce that enables seamless data exchange between different Lightning Web Components (LWCs) and other components on a Lightning page.

How to Use LMS in LWC:

  1. Create a Lightning Message Channel:

    • Define a channel using the @salesforce/messageChannel module.

    • Assign a unique name to the channel, typically with a __c suffix.

  2. Publish a Message:

    • Import the MessageContext interface from @salesforce/messageChannel.

    • Obtain a reference to the message channel using the MessageContext object.

    • Use the publish() method to send a message to the channel.

  3. Subscribe to a Message:

    • Import the subscribe() method from @salesforce/messageChannel.

    • Obtain a reference to the message channel using the MessageContext object.

    • Use the subscribe() method to listen for messages on the channel.

    • Implement a callback function to handle the received messages.

Note :

  • You need to create and .xml file for passing event/eventName in publish() and subscribe()

  • In XML master label tag will contain the eventname which will be eventually you file name below is the syntax.

  • Syntax : YourEventName.messageChannel-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>MessageEvent</masterLabel>
    <description>This Lightning Message Channel sends information from VF to LWC</description>
    <isExposed>true</isExposed>
</LightningMessageChannel>
  • Component1LMS.html
<template>
    <lightning-card>
        <lightning-button label="Publish Message" onclick={handlePublish}></lightning-button>
    </lightning-card>
</template>
  • Component1LMS.js
import { MessageContext, publish } from 'lightning/messageService';
import { LightningElement, wire } from 'lwc';
import MESSAGEEVENT from "@salesforce/messageChannel/MessageEvent__c"

export default class Component1LMS extends LightningElement {

    @wire(MessageContext)
    messageContext;

    handlePublish() {
        const payload = {data: 'Hey bro!'};
        publish(this.messageContext, MESSAGEEVENT, payload);
    }
}
  • Component2LMS.html
<template>
    <lightning-card>
        Message from Parent is {message}
    </lightning-card>    
</template>
  • Component2LMS.js
import { MessageContext, subscribe, unsubscribe } from 'lightning/messageService';
import { LightningElement, track, wire } from 'lwc';
import MESSAGEEVENT from "@salesforce/messageChannel/MessageEvent__c"

export default class Component2LMS extends LightningElement {

    @wire(MessageContext)
    messageContext;

    @track message = '';

    connectedCallback() {
        subscribe(this.messageContext, MESSAGEEVENT, (payload) => this.handleEvent(payload));
    }

    disconnectedCallback() {
        unsubscribe(this.handleEvent);
        this.handleEvent = null;
    }

    handleEvent(payload) {
        this.message = payload.data;
    }
}

What is import MESSAGEEVENT from "@salesforce/messageChannel/MessageEvent__c"

  • MESSAGEEVENT is a Message Channel passed in subscribe() and publish() method as event name.

MessageContext:

  • The MessageContext object provides information about the Lightning web component that is using the Lightning message service. Get this object via the MessageContext wire adapter or via createMessageContext().

publish(messageContext, messageChannel, message)

  • Publishes a message to a specified message channel.
ParameterTypeDescription
messageContextobjectThe MessageContext object provides information about the Lightning web component that is using the Lightning message service. Get this object via the MessageContext wire adapter or via createMessageContext().
messageChannelobjectThe message channel object. To import a message channel, use the scoped module @salesforce/messageChannel. To create a message channel in an org, use the LightningMessageChannel metadata type.
messageobjectA serializable JSON object containing the message published to subscribers. A message can't contain functions or symbols.

subscribe(messageContext, messageChannel, listener, subscriberOptions)

  • Subscribes to a specified message channel. Returns a Subscription object that you can use to unsubscribe.
ParameterTypeDescription
messageContextobjectThe MessageContext object provides information about the Lightning web component that is using the Lightning message service.
messageChannelobjectTo import a message channel, use the scoped module @salesforce/messageChannel. To create a message channel in an org, use the LightningMessageChannel metadata type.
listenerfunctionA function that handles the message once it is published.
subscriberOptionsobject(Optional) An object that, when set to {scope: APPLICATION_SCOPE}, specifies the ability to receive messages on a message channel from anywhere in the application. Import APPLICATION_SCOPE from lightning/messageService.

unsubscribe(subscription)

  • Unsubscribes from a message channel.
ParameterTypeDescription
subscriptionobjectThe Subscription object returned by the subscribe() function.

What is Navigation in LWC

In Lightning Web Components (LWC), navigation is a fundamental aspect of creating dynamic and user-friendly applications. It involves seamlessly moving users between different pages, records, and components within your Salesforce org.

Key Concepts:

  1. Navigation Service:

    • The lightning/navigation service provides the core functionality for navigating within your LWC applications.

    • It uses PageReference objects to define the target destination.

  2. PageReference:

    • A JavaScript object that encapsulates the details of the page to navigate to, including:

      • type: Specifies the type of page (e.g., standard__recordPage, standard__objectPage).

      • attributes: Defines specific parameters like record ID, object API name, and action name.

      • state: Optional state parameters to pass to the target page.

Core Navigation Methods:

  1. [NavigationMixin.Navigate](pageReference, [replace]):

    • Navigates to the specified PageReference.

    • The optional replace parameter determines whether to replace the current history entry or create a new one.

    navigateToObjectHome() {
            // Navigate to the Account home page
            this[NavigationMixin.Navigate]({
                type: 'standard__objectPage',
                attributes: {
                    objectApiName: 'Account',
                    actionName: 'home',
                },
            });
        }

[NavigationMixin.GenerateUrl](pageReference):

  • Generates a URL based on the given PageReference.

  • Useful for creating links or opening URLs in new tabs or windows.

    recordPageUrl;

    connectedCallback() {
        // Generate a URL to a User record page
        this[NavigationMixin.GenerateUrl]({
            type: 'standard__recordPage',
            attributes: {
                recordId: '005B0000001ptf1IAE',
                actionName: 'view',
            },
        }).then((url) => {
            this.recordPageUrl = url;
        });
    }

Extending LightningElement:

  • In LWC you extend LightningElement by default when creating a component, but when you want to use the NavigationMixin, you don’t directly extend LightningElement. Instead you use a decorator pattern where NavigationMixin is applied on top of LightningElement.

  • This is because LWC does not allow multiple inheritance.

How Does the NavigationMixin Work?

  • NavigationMixin is a function which takes a base class as argument (usually LightningElement) and return a new Class with navigation functionality mixed in. By this approach , it wraps LightningElement to add new features.

  • Example:

        import {LightningElement} From 'lwc';
        import {NavigationMixin} from 'lightning/navigation';
    
        export default class YourComponent extends NavigationMixin(LightningElement) {
            // your component logic
        }