Saturday 11 March 2023

LWC Tutorial

  LWC:


Lightning web component is a new programming model to developer salesforce lightning components. It's a UI framework that is built using native HTML and javascript.


1. Decorators:

Decorator is a special kind of declaration that can change behaviour of method or property.


LWC support 3 decorators. (@track, @api, @wire)

@track: To make property private reactive.

@api: To make property public reactive.

@wire: To read data from Salesforce and store into cache.


2. Lifecycle hooks:


A lifecycle hook is a callback method triggered at a specific phase of a component instance’s lifecycle.
Lightning web components have a lifecycle managed by the framework. The framework creates components, inserts them into the DOM, renders them, and removes them from the DOM. It also monitors components for property changes. Generally, components don’t need to call these lifecycle hooks, but it is possible.

  • constructor()
  • connectedCallback()
  • disconnectedCallback()
  • render()
  • renderedCallback()
  • errorCallback(error, stack)

  • Run Code When a Component Is Created
    The constructor() method fires when a component instance is created. Don’t add attributes to the host element during construction. You can add attributes to the host element in any other lifecycle hook.

  • Run Code When a Component Is Inserted or Removed from the DOM
    The connectedCallback() lifecycle hook fires when a component is inserted into the DOM. The disconnectedCallback() lifecycle hook fires when a component is removed from the DOM.
  • Run Code When a Component Renders
    The renderedCallback() is unique to Lightning Web Components. Use it to perform logic after a component has finished the rendering phase.
  • Handle Component Errors
    The errorCallback() is unique to Lightning Web Components. Implement it to create an error boundary component that captures errors in all the descendent components in its tree. It captures errors that occur in the descendant’s lifecycle hooks or during an event handler declared in an HTML template. You can code the error boundary component to log stack information and render an alternative view to tell users what happened and what to do next.

<template> <template if:true={error}> <lightning-card title="Error Occurred"> <div>Error Occurred</div> </lightning-card> </template> <template if:false={error}> <lightning-card title="Error not Occurred"> <div>Error did not occur</div> </lightning-card> </template> </template>


import { LightningElement, track } from 'lwc' import templatePrimary from './sampleFirstTemplate.html'; import templateSecondary from './sampleSecondTemplate.html'; export default class SampleHelloWorld extends LightningElement{ showTemplatePrimary = true; @track _isRendered = true;//boolean to check if component is rendered error; stack; constructor() { super();//Calling Constructor of LightningElement console.log('Constructor called =>'); } connectedCallback() { let varElement = this.template; console.log('ConnectedCallback called =>'+varElement.isConnected); } render() { console.log('Render called =>'+this.showTemplatePrimary); return this.showTemplatePrimary ? templatePrimary : templateSecondary; } renderedCallback() { if(this._isRendered) { console.log('Parent Component renderedCallback =>'); this._isRendered = false; } } disconnectedCallback() { console.log('Disconnected Callback =>'); } errorCallback(error, stack){ console.log('Error callBack called =>'); this.error = error; this.stack = stack; } }

sampleFirstTemplate.html

<!-- firstTemplate.html -->
<template>
    <lightning-card>
        <div>
            Primary Template Rendered
        </div>
    </lightning-card>
 
    <c-child-component-test></c-child-component-test>
 
</template>

sampleSecondTemplate.html

<!-- secondTemplate.html -->
<template>
    <lightning-card title="Template Two">
        <div>
            Secondary Template rendered
        </div>
    </lightning-card>
</template>

sampleHelloWorld.js-meta.xml

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


3. Display and Update records using button in LWC: (Wire, Imperative)
 
In this example, we are using wire apex methods to LWC to display records.

Apex Class:

public  class AccountController 
{
   @AuraEnabled(cacheable=true)
   public static List<Account> displayAccounts(){
       return [select Id,Name,Site from Account];
   }
   @AuraEnabled
   public static List<Account> updateRecord(String accId){
       Account acc=[select Id,Name,Site from Account where Id=:accId];
       acc.site='Approved';
       try{
           update acc;
       }
   catch (Exception e) {
           System.debug('unable to update the record due to'+e.getMessage());
       }
       return displayAccounts();
   }
}

DisplayAndUpdateAccount.html

<template>
<table class="slds-table slds-table_cell-buffer slds-table_bordered">
<tr>
<td><b>Name</b></td>
<td><b>Site</b></td>
<td><b>Action</b></td>
</tr>
<template for:each={accounts.data} for:item="acc">
<tr key={acc.Id}>
<td>
{acc.Name}
</td>

<td>
<lightning-button label="update"  variant="brand" value={acc.Id} onclick={handleUpdate}>
</lightning-button>
</td>
</tr>
</template>
</table>
</template>
</template>

DisplayAndUpdateAccount.js

import { LightningElement,api,wire } from 'lwc';
import displayAccounts from '@salesforce/apex/AccountController.displayAccounts';
import updateRecord from '@salesforce/apex/AccountController.updateRecord';
import { refreshApex } from '@salesforce/apex';
export default class UpdateAccount extends LightningElement {
@api currentRecordId;
@api errorMessage;
    @wire(displayAccounts) accounts;
    handleUpdate(event){
        this.currentRecordId=event.target.value;
        console.log('@@currentRecordId@@@'+this.currentRecordId);
        updateRecord({
            accId:this.currentRecordId
        })
        .then(() => {
            console.log('SUCCESS');
            return refreshApex(this.accounts);
        })
        .catch((error) => {
            this.errorMessage=error;
console.log('unable to update the record due to'+JSON.stringify(this.errorMessage));
        });
    }
}

4. Call Apex Methods Imperatively:

public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != NULL
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

// apexImperativeMethod.js
import { LightningElement, track } from 'lwc';
import getContactList from '@salesforce/apex/ContactController.getContactList';

export default class ApexImperativeMethod extends LightningElement {
    @track contacts;
    @track error;

    handleLoad() {
        getContactList()
            .then(result => {
                this.contacts = result;
            })
            .catch(error => {
                this.error = error;
            });
    }
}
<!-- apexImperativeMethod.html -->
<template>
    <lightning-card title="ApexImperativeMethod" icon-name="custom:custom63">
        <div class="slds-m-around_medium">
            <p class="slds-m-bottom_small">
                <lightning-button label="Load Contacts" onclick={handleLoad}></lightning-button>
            </p>
            <template lwc:if={contacts}>
                <template for:each={contacts} for:item="contact">
                    <p key={contact.Id}>{contact.Name}</p>
                </template>
            </template>
            <template lwc:elseif={error}>
                <c-error-panel errors={error}></c-error-panel>
            </template>
        </div>
    </lightning-card>
</template>

5. LDS:

Lightning Data Service (LDS) is an intermediate layer between Lightning Components (both LWC and Aura) and User Interface API of Salesforce,

In lightning web components you can use LDS in two different ways

  • Standard Lightning web components lightning-record-edit-formlightning-record-form, and lightning-record-view-form. -- Lightning record forms.
  • Lightning UI APIs, like uiRecordApiuiObjectApi which contains wired adapters functions.
import { createRecord } from 'lightning/uiRecordApi'; used to create record.

getObjectInfo

Use this wire adapter to get metadata about a specific object. The response includes metadata describing the object’s fields, child relationships, record type, and theme.

Syntax
import { LightningElement, wire } from 'lwc';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';

export default class Example extends LightningElement {
    @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
    propertyOrFunction;
}

lightningRecordFormEditExampleLWC.html

1
2
3
4
5
<template>
    <lightning-record-form record-id={recordId} object-api-name={objectApiName}
        fields={fields} columns="2" mode="edit" onsubmit={handleSubmit}>
    </lightning-record-form>
</template>

lightningRecordFormEditExampleLWC.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { LightningElement, api } from 'lwc';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
export default class LightningRecordFormEditExampleLWC extends LightningElement {
    @api recordId;
    @api objectApiName;
    fields = [NAME_FIELD, REVENUE_FIELD, INDUSTRY_FIELD];
    handleSubmit(event){
        //you can change values from here
        //const fields = event.detail.fields;
        //fields.Name = 'My Custom  Name'; // modify a field
        console.log('Account detail : ',event.detail.fields);
        console.log('Account name : ',event.detail.fields.Name);
    }
}

lightningRecordFormEditExampleLWC.js-meta.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>48.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Now we can add this LWC component on the Account Record page.

      We can change html file to show full page layout

      lightningRecordFormEditExampleLWC.html


      1
      2
      3
      4
      5
      <template>
          <lightning-record-form record-id={recordId} object-api-name="Account" layout-type="Full" columns="2" mode="edit"
             onsuccess={handleSuccess} onsubmit={handleSubmit}>
          </lightning-record-form>
      </template>


    Creating a Record using lightning-record-form LWC

    lightningRecordFormCreateExampleLWC.html

    1
    2
    3
    4
    5
    <template>
        <lightning-record-form object-api-name={objectApiName}
            fields={fields} onsuccess={handleSuccess}>
        </lightning-record-form>
    </template>

    lightningRecordFormCreateExampleLWC.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { LightningElement, api } from 'lwc';
    import { ShowToastEvent } from 'lightning/platformShowToastEvent';
    import NAME_FIELD from '@salesforce/schema/Account.Name';
    import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
    import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
    export default class LightningRecordFormCreateExampleLWC extends LightningElement {
        // objectApiName is "Account" when this component is placed on an account record page
        @api objectApiName;
        fields = [NAME_FIELD, REVENUE_FIELD, INDUSTRY_FIELD];
        handleSuccess(event) {
            const evt = new ShowToastEvent({
                title: "Account created",
                message: "Record ID: " + event.detail.id,
                variant: "success"
            });
            this.dispatchEvent(evt);
        }
    }

    lightningRecordFormCreateExampleLWC.js-meta.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="UTF-8"?>
    <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
        <apiVersion>48.0</apiVersion>
        <isExposed>true</isExposed>
        <targets>
            <target>lightning__AppPage</target>
            <target>lightning__RecordPage</target>
            <target>lightning__HomePage</target>
        </targets>
    </LightningComponentBundle>

    Now we can add this LWC component on the Account Record page.

      Let’s create an example to create account record using lightning-record-edit-form in Lightning Web Component

      recordEditFormCreateExampleLWC.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <template>
          <lightning-record-edit-form object-api-name="Contact" onsuccess={handleSuccess} onsubmit ={handleSubmit}>
              <lightning-messages>
              </lightning-messages>
              <lightning-input-field field-name='AccountId'></lightning-input-field>
              <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 class="slds-m-top_small" type="submit" label="Create Contact">
              </lightning-button>
          </lightning-record-edit-form>
      </template>

      recordEditFormCreateExampleLWC.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      import { LightningElement} from 'lwc';
      export default class RecordEditFormCreateExampleLWC extends LightningElement {
          handleSuccess(event) {
              console.log('onsuccess event recordEditForm',event.detail.id)
          }
          handleSubmit(event) {
              console.log('onsubmit event recordEditForm'+ event.detail.fields);
          }
      }

      recordEditFormCreateExampleLWC.js-meta.xml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <?xml version="1.0" encoding="UTF-8"?>
      <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
          <apiVersion>48.0</apiVersion>
          <isExposed>true</isExposed>
          <targets>
              <target>lightning__AppPage</target>
              <target>lightning__RecordPage</target>
              <target>lightning__HomePage</target>
          </targets>
      </LightningComponentBundle>

      Now we can add this LWC component on the Contact Record page.

      • Go to Contact record.
      • Click Setup (Gear Icon) and select Edit Page.
      • Under Custom Components, find your recordEditFormCreateExampleLWC component and drag it on Contact Page.
      • Click Save and activate.

      6. Child To Parent Communication using events.

      Dynamic search functionality in Lightning Web Component with event:

      public with sharing class AccountDataController {
          public AccountDataController() {

          }
          @AuraEnabled
          public static List<Account> displayAccounts(String searchekey){
              String searchword='%'+searchekey+'%';
              List<Account> returnlist=new List<Account>();
              if(!String.isBlank(searchekey))
              for(Account acc:[select Id,Name,Site from Account where Name like:searchword]){
                  returnlist.add(acc);

              }
              return returnlist;
          }
      }
      searchAccountComponent.html
      <template>
          <lightning-input type="text"  class="accName" label="SearchAccounts" onchange={handleSearch}>
          </lightning-input>
      </template>

      searchAccountComponent.js
      import { LightningElement,api } from 'lwc';

      export default class SearchAccountComponent extends LightningElement {
          
          @api userInput;
          handleSearch(event){
              this.userInput=event.target.value;
              const newEvent=new CustomEvent('inputcarryevent',{
                  detail:this.userInput

              });
              this.dispatchEvent(newEvent);

          }
      }

      accountDataComponent.html
      <template>
          <c-search-account-component oninputcarryevent={displayData}>
          </c-search-account-component><br/><br/>
          <div if:true={isshow}>
              <table class="slds-table slds-table_cell-buffer slds-table_bordered">
                  <tr>
                      <td><b>Name</b></td>
                      <td><b>Site</b></td>
                  </tr>
                  <template for:each={records} for:item="acc">
                      <tr key={acc.Id}>
                          <td>
                              {acc.Name}
                          </td>
                          <td>
                              {acc.Site}
                          </td>

                      </tr>
                  </template>
              </table>
          </div>
      </template>
      accountDataComponent.js
      import { LightningElement,api } from 'lwc';
      import displayAccounts from '@salesforce/apex/AccountDataController.displayAccounts';

      export default class AccountDataComponent extends LightningElement {
          @api records;
          @api error;
          @api isshow;
          displayData(event){
              console.log('@@@@Entered into the method@@@');
              displayAccounts({
                  searchekey:event.detail
              })
              .then(result=>{
                  this.records=result;
                  console.log('@@@records'+JSON.stringify(this.records));
                  if(result.length!=0){
                      this.isshow=true;
                  }
                  else{
                      this.isshow=false;
                  }
                  this.error=undefined;

              })
              .catch(error=>{
                  this.records=undefined;
                  this.error=error;
              });
          }
      }

      LightningApplication
      <aura:application extends="force:slds" >
          <c:accountDataComponent/>
      </aura:application>

      7. Parent to Child Communication using api .

      Decorate a method with @api in the child component to allow a parent component to call methods in the child component. Let’s understand this with the below example.

       We would be updating the childLwc and parentLwc components created above for this example.

       

      Add the following code in childLwc component:

       import { LightningElement, api } from 'lwc';

      export default class ChildLwc extends LightningElement {

      message;

      @api greet(message) {

      // Suppose a translating capability is here which translate the message param to local user's language

      this.message = message;

      }

      }

      At Line 8 in the above code, we have decorated the greet method with @api so that it is publically available to be called from the parent component.

       <template>

       <lightning-card title="Child LWC">

       <p class="slds-p-horizontal_small">

       Message In Child Component : {message}

       </p>

       </lightning-card>

       </template>

       

      Add the following code in parentLwc component:

      <template>

      <lightning-card title="Parent LWC">

      <lightning-button variant="brand" label="Send Message" title="Send Message" slot = "actions" onclick={parentHandler}></lightning-button>


      <c-child-lwc onsendmessage={parentHandler}></c-child-lwc>

      </lightning-card>

      </template>

      import { LightningElement } from 'lwc'; 

      export default class ParentLwc extends LightningElement {

      parentHandler(event) {

      this.template.querySelector('c-child-lwc').greet('Hey, This Message Is Sent From Parent!');

       

      } }

      In the above code, we are calling the child method by using this.template.querySelector(‘c-child-lwc’)

      querySelector() returns the first Element within the document that matches the specified selector.

      View the parentLwc component on the lightning app page, you would be getting the below output.


      8. Communication between unrelated components using LMS (Lightning Message Service).





      Step 1 : Here we will create a Lightning Message Service (LMS) file in the messageChannels folder in the default branch.

      lightning-message-service-(lms)-in-lwc-folder-creatation
      lightning-message-services-(lms)-in-lwc-folder-creatation

      TechdicerChannel.messageChannel-meta.xml :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <?xml version="1.0" encoding="UTF-8"?>
      <LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
          <description>This is a Lightning Message Service Channel.</description>
          <isExposed>true</isExposed>
          <lightningMessageFields>
              <description>This is sample message</description>
              <fieldName>message</fieldName>
          </lightningMessageFields>
          <masterLabel>TechdicerChannel</masterLabel>
      </LightningMessageChannel>

      Step 2: In this step, we will create two LWC component which is not related to each other, one is the Publisher and another one is the Subscriber component.

      lWCLMS.HTML : this is the Publisher component.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <template>
          <lightning-card title="Lightning Message Service (LMS) in LWC Publisher" icon-name="standard:contact">
              <div class="slds-p-horizontal_small">
                  <lightning-input type="text" label="Message" onchange={handleChange} value={message}></lightning-input>
                  <!-- call subscriber -->
                  <div class="slds-p-around_medium lgc-bg" style="text-align: end;">
                      <lightning-button label="Fire Event" variant="brand" onclick={handleClick}></lightning-button>
                  </div>   
              </div>
          </lightning-card>
      </template>

      lWCLMS.JS :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      import { LightningElement, wire } from 'lwc';
      import TechdicerChannel from '@salesforce/messageChannel/TechdicerChannel__c';
      import {publish, MessageContext} from 'lightning/messageService'
       
      export default class LWCLMS extends LightningElement {
          @wire(MessageContext)
          messageContext;
          message;
       
          handleChange(event){
              this.message = event.detail.value;
          }
       
          handleClick() {
              let message = {message: this.message};
              publish(this.messageContext, TechdicerChannel, message);
          }
      }

      subscriber.HTML : This is the Subscriber Component.

      1
      2
      3
      4
      5
      6
      7
      <template>
          <lightning-card title="Lightning Message Service (LMS) in LWC Subscriber" icon-name="standard:contact">
              <div class="slds-m-around_medium">
                  {publisherMessage}
              </div>
          </lightning-card>
      </template>

      subscriber.JS :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      import { LightningElement, wire } from 'lwc';
      import TechdicerChannel from '@salesforce/messageChannel/TechdicerChannel__c';
      import { subscribe, MessageContext } from 'lightning/messageService';
      import { ShowToastEvent } from 'lightning/platformShowToastEvent';
       
      export default class Subscriber extends LightningElement {
          publisherMessage = '';
          subscription = null;
       
          @wire(MessageContext)
          messageContext;
       
          connectedCallback() {
              this.handleSubscribe();
          }
       
          handleSubscribe() {
              if (this.subscription) {
                  return;
              }
              this.subscription = subscribe(this.messageContext, TechdicerChannel, (message) => {
                  console.log(message.message);
                  this.publisherMessage = message.message;
                  this.ShowToast('Success', message.message, 'success', 'dismissable');
              });
          }
       
          ShowToast(title, message, variant, mode){
              const evt = new ShowToastEvent({
                  title: title,
                  message:message,
                  variant: variant,
                  mode: mode
              });
              this.dispatchEvent(evt);
          }
           
      }

      Output :

      lightning-message-service-(lms)-in-lwc-output

      ------------------------------------------------------

      Async and Await in Lightning Web Components

      Async and Await in Lightning Web Components
      Asynchronous programming is a crucial aspect of web development, and it becomes even more important when dealing with large-scale applications.

      In traditional JavaScript, asynchronous programming is typically handled with callbacks or promises. However, the introduction of the async/await syntax has greatly simplified asynchronous programming in JavaScript. This syntax is available in Lightning Web Components as well and can help to simplify your code and make it more readable.


      What is Async and Await?


      Async/await is essentially a way to write asynchronous code that looks and behaves more like synchronous code. Async/await is built on top of promises, which are used to handle asynchronous operations in JavaScript.


      The async keyword is used to define an asynchronous function, which returns a promise. Within this function, you can use the await keyword to pause the execution of the function until a promise is resolved or rejected. This makes your code look more like synchronous code, as the execution is blocked until the promise resolves.


      How to use Async and Await in Lightning Web Components


      Now that we understand what async/await is, let's take a look at how we can use it in Lightning Web Components. The first step is to define an asynchronous function. Here's an example of an asynchronous function that returns a promise:


      async function fetchData() {

      const response = await fetch('/api/data');

      const data = await response.json();

      return data;

      }


      In this example, we're using the fetch API to make a request to an API endpoint. We're using the await keyword to pause the execution of the function until the promise returned by fetch is resolved.

      Once the promise is resolved, we parse the response body as JSON using the response.json() method. Finally, we return the resulting data.

      To use this function in a Lightning Web Component, we can simply call it from the component's JavaScript file:


      import { LightningElement } from 'lwc';

      import fetchData from './fetchData';


      export default class MyComponent extends LightningElement {

      async connectedCallback() {

      const data = await fetchData();

      console.log(data);

      }

      }


      Promise with Async and Await


      import { LightningElement, api } from 'lwc';

      import fetchData from '@salesforce/apex/MyController.fetchData';


      export default class MyComponent extends LightningElement {

      @api recordIds;

      data = [];


      async connectedCallback() {

      const promises = this.recordIds.map(id => fetchData({ recordId: id }));

      const results = await Promise.all(promises);

      this.data = results;

      }

      }


      Common Use Cases for Async and Await


      As you can see, the use of async and await is not limited to just making API calls. In fact, they're very useful when you want to perform an action that takes a long time. For example:


      • Making HTTP requests

      • Waiting for an asynchronous action to finish

      • Processing multiple promises in parallel

      Error Handling with Async and Await


      As you've seen, async functions return promises. If an exception is thrown in an async function, the promise it returns will be rejected with that exception. This can be handled by catching errors with try/catch blocks as usual:


      try {

      await someAsyncFunction(arg1); // do something else here...

      } catch(e) { // handle error here... }