top of page

Dynamically Import and Instantiate Lightning Web Components

Writer: S. ChaudharyS. Chaudhary


Lightning Web Components (LWC) empower you to build dynamic and interactive user interfaces for Salesforce. But what if you could take it a step further and dynamically load components based on specific needs? This is where dynamic import and instantiation come in, unlocking new possibilities for LWC development.


Why Dynamic?

Traditional LWC development involves statically importing all components upfront. This approach can be inefficient for scenarios where:

  • Large modules: You only need specific components within a larger module under certain conditions. Loading the entire module all the time can impact performance.

  • Runtime selection: The component to render depends on user interaction or data fetched at runtime. Static imports wouldn't work in such cases.


The Dynamic Approach

Dynamic import and instantiation offer a solution by:

  1. Import on demand: Using the import() function, you can import components only when required. This optimizes initial page load times.

  2. Runtime selection: The lwc:component element with the lwc:is directive acts as a placeholder in the template. You can dynamically set the lwc:is value to the imported component constructor at runtime, allowing for flexible component selection.


Benefits of Going Dynamic

  • Improved Performance: Only load components are needed, leading to faster page loads and a smoother user experience.

  • Enhanced Flexibility: Adapt your UI based on user actions or data, creating more dynamic and interactive applications.

  • Reduced Bundle Size: Smaller initial bundles as unnecessary components are loaded only when needed.




Let’s see an example!

Implement a dynamic instantiation feature for Lightning Web Components (LWC) in Salesforce, allowing components to be loaded and rendered based on user interaction or specific runtime conditions. This feature should optimize performance by only loading necessary components, enhance flexibility by adapting the UI dynamically, and reduce bundle size by avoiding unnecessary imports.


The images below display the result we will achieve at the end of the blog.


Codebase for the requirement 


dynamicInstantiateCmp.html


  • This component manages the dynamic instantiation of child components based on user selection.

  • It includes a radio group to choose which component to load dynamically.

  • Handles importing the selected component using the import() function and sets it as the value for lwc:component directive.

<template>
    <lightning-card>
        <div class="slds-p-around_small">
            <h1><strong>{label.HEADING_TEXT}</strong></h1>
            <lightning-radio-group label={label.SELECT_COMPONENT_TO_SHOW} options={options} value={value}
                type="radio" onchange={handleChange}></lightning-radio-group>
            <template if:true={isChildCmp}>
                <lightning-input type="text" label={label.MESSAGE_FOR_CHILD_COMPONENT} onchange={handleMessage}></lightning-input>
            </template>
            <div class="container">
                <lwc:component lwc:is={componentConstructor} message={message}></lwc:component>
            </div>
        </div>
    </lightning-card>
</template>

dynamicaInstantiateCmp.js

------------------------------------------------------------------------*
Version History:*
VERSION    DEVELOPER NAME        DATE         DETAIL FEATURES
1.0        Santosh Kumar      06/03/2024     Initial Development
1.1        Shiv K Chaudhary   23/09/2024     Update JS and Html
***********************************************************************/
import {LightningElement} from 'lwc';
import HEADING_TEXT from "@salesforce/label/c.Dynamic_Component_Header_Text";
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
import SELECT_COMPONENT_TO_SHOW from "@salesforce/label/c.SELECT_COMPONENT_TO_SHOW";
import MESSAGE_FOR_CHILD_COMPONENT from "@salesforce/label/c.MESSAGE_FOR_CHILD_COMPONENT";
import LOAD_ACCOUNTCONTACT_COMPONENT from "@salesforce/label/c.LOAD_ACCOUNTCONTACT_COMPONENT";
import LOAD_DYNAMIC_CHILD_COMPONENT from "@salesforce/label/c.LOAD_DYNAMIC_CHILD_COMPONENT";
import ERROR from "@salesforce/label/c.ERROR";
import ERROR_IMPORTING_COMPONENT from "@salesforce/label/c.ERROR_IMPORTING_COMPONENT";
import ACCOUNT_CONTACTS_DISPLAY from "@salesforce/label/c.ACCOUNT_CONTACTS_DISPLAY";
import DYNAMIC_CHILD from "@salesforce/label/c.DYNAMIC_CHILD";

export default class DynamicInstantiateCmp extends LightningElement {
    componentConstructor;
    getComponent;
    message;
    isChildCmp = false;
    label = {
        HEADING_TEXT,
        SELECT_COMPONENT_TO_SHOW,
        MESSAGE_FOR_CHILD_COMPONENT
    }

    // Purpose : Get label and value for radio buttons
    get options() {
        return [
            {label: LOAD_ACCOUNTCONTACT_COMPONENT, value: ACCOUNT_CONTACTS_DISPLAY},
            {label: LOAD_DYNAMIC_CHILD_COMPONENT, value: DYNAMIC_CHILD},
        ];
    }

    // Purpose: This method get value of radio button and insert dynamic component
    handleChange(event) {
        this.getComponent = event.target.value;
        if (this.getComponent == DYNAMIC_CHILD) {
            this.isChildCmp = true;
        }
        else {
            this.isChildCmp = false;
        }
        import("c/" + this.getComponent)
            .then(({ default: ctor }) => (this.componentConstructor = ctor))
            .catch((err) => this.showErrorToast());
    }

    // Purpose : This method get text value from lightning input and set it to message
    handleMessage(event) {
        this.message = event.target.value;
    }

    // Purpose : This method used to Error Toost message when Error occur
    showErrorToast() {
        const event = new ShowToastEvent({
            title: ERROR,
            message: ERROR_IMPORTING_COMPONENT,
            variant: 'error',
            mode: 'dismissable'
        });
        this.dispatchEvent(event);
    }
}

dynamicInstantiateCmp.js-meta.xml

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

dynamicChild.html


  • This component serves as a child component to display a message passed from the parent component.

  • It receives the message as a property (@api) and renders it within a <p> tag.

<template>
    <lightning-card>
        <h1>{label.CHILD_COMPONENT}</h1>
        <p>{label.MESSAGE_FROM_PARENT} {message}</p>
    </lightning-card>
</template>

dynamicChild.js

/************************************************************************
(c) Copyright 2023 Avenoir Technologies Pvt. Ltd. All rights reserved.*
Created by: Santosh Kumar
Ticket Number: AVEBLOG-95
------------------------------------------------------------------------*
Blog: Dynamically Import and Instantiate Lightning Web Components
------------------------------------------------------------------------*
Version History:*
VERSION    DEVELOPER NAME        DATE         DETAIL FEATURES
1.0        Santosh Kumar      06/03/2024     Initial Development
1.1        Shiv K Chaudhary   23/09/2024     Update JS and Html
***********************************************************************/
import {LightningElement, api} from 'lwc';
import MESSAGE_FROM_PARENT from "@salesforce/label/c.MESSAGE_FROM_PARENT";
import CHILD_COMPONENT from "@salesforce/label/c.CHILD_COMPONENT";

export default class DynamicChild extends LightningElement {
    @api message;
    label = {
        MESSAGE_FROM_PARENT,
        CHILD_COMPONENT
    }
}

dynamicChild.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>

accuntContactsDisplay.html


  • This component displays a list of accounts and their associated contacts.

  • It fetches account data using a wired Apex method (getAccounts) and iterates over the results to display them in a table format.

<template>
    <lightning-card class="card">
        <div class="container">
            <table border="solid">
                <thead>
                    <th>{Id}</th>
                    <th>{Name}</th>
                </thead>
                <tbody>
                    <template for:each={accounts} for:item="acc">
                        <tr key={acc.account.Id}>
                            <td>{acc.account.Id}</td>
                            <td>
                                <div onclick={openModal}><a id={acc.account.Id} value={acc.account.Id}>{acc.account.Name}</a></div>
                            </td>
                        </tr>
                    </template>
                </tbody>
                <template if:true={isModalOpen}>
                    <!-- Modal/Popup Box LWC starts here -->
                    <section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true"
                        aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
                        <div class="slds-modal__container">
                            <!-- Modal/Popup Box LWC header here -->
                            <header class="slds-modal__header">
                                <button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse"
                                    title="Close" onclick={closeModal}>
                                    <lightning-icon icon-name="utility:close" alternative-text="close" variant="inverse"
                                        size="small"></lightning-icon>
                                    <span class="slds-assistive-text">Close</span>
                                </button>
                                <h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">{accountName}</h2>
                            </header>
                            <!-- Modal/Popup Box LWC body starts here -->
                            <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
                                <template if:true={checkTrue}>
                                    <ul style=" list-style-type: circle;padding-left:7%">
                                    <table class="modalTable">
                                        <tbody style="text-align:center">
                                        <template for:each={contactListShown} for:item="con">
                                            <tr key={con.key}>
                                                <td> <b>{con.Value}</b></td>
                                                <td class="viewBtn">
                                                    <lightning-button value={con.key} label="View Contact" onclick={navigateToViewContactPage}></lightning-button>
                                                </td>
                                            </tr>
                                        </template>
                                    </tbody>
                                    </table>
                                    </ul>

                                </template>
                                <template if:false={checkTrue}>
                                    <p>{NoContacts}</p>
                                </template>
                            </div>
                        </div>
                    </section>
                    <div class="slds-backdrop slds-backdrop_open"></div>
                </template>
            </table>
        </div>
    </lightning-card>
</template>

accountContactsDisplay.js

import {LightningElement, wire,track} from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccountList';
import Name from '@salesforce/label/c.Name';
import Id from '@salesforce/label/c.Id';
import NoContacts from '@salesforce/label/c.No_Contacts';
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
import {NavigationMixin} from 'lightning/navigation';

export default class AccountContactsDisplay extends NavigationMixin(LightningElement) {
    accounts;
    AccountMap = [];
    @track accountName;
    accountList = [];
    @track checkTrue = false;
    @track contactListShown;
    @track isModalOpen = false;
    Id = Id;
    Name = Name;
    NoContacts = NoContacts;

    @wire(getAccounts) getAccount({data,error}) {
        if(data){
            this.accounts=data;
            for(let i = 0; i < data.length ; i++) {
                let contacts = data[i]["account"]["Contacts"];
                let contactList = [];
                if(contacts){
                    for(let j = 0; j < contacts.length; j++){
                        let Name = contacts[j]["FirstName"] != null? contacts[j]["FirstName"] + contacts[j]["LastName"]:contacts[j]["LastName"];
                        contactList.push({"key":contacts[j]["Id"], "Value":Name});
                    }
                }
                this.AccountMap.push({"key":data[i]["account"]["Id"]+","+data[i]["account"]["Name"], "value":JSON.stringify(contactList)});
            }
        }
        else if(error) {
            this.ShowToastEventError(error);
        }
    };

     // Navigate to View Contact Page
     navigateToViewContactPage(event) {
        let key = event.target.value;
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                "recordId": key,
                "objectApiName": "Contact",
                "actionName": "view",
            },
        });
    }

    handleClick(event) {
       let value = event.target.id;
       let Id = value.substring(0,18);
       this.AccountMap.forEach(element => {
            let key = JSON.stringify(element["key"]);
            let matchID = key.split(",")[0];
            if(matchID.match(Id)){
                let name =  key.split(",")[1];
                let len = name.length;
                this.accountName = name.substring(0,len-1);
                let contacts = element["value"];
                if(contacts.length > 2){
                    for(let i = 0; i < contacts.length ; i++) {
                        this.contactListShown = JSON.parse(contacts);
                    }
                    this.checkTrue = true;
                }
                else{
                    this.checkTrue = false;
                }
            }
        });
    }

    ShowToastEventError(error) {
        const event = new ShowToastEvent({
                title: "Error",
                message: error,
                variant: "Error",
        });
        this.dispatchEvent(event);
    }

    openModal(event) {
        // to open modal set isModalOpen tarck value as true
        this.isModalOpen = true;
        this.handleClick(event);
    }

    closeModal() {
        // to close modal set isModalOpen tarck value as false
        this.isModalOpen = false;
    }

    submitDetails() {
        // to close modal set isModalOpen tarck value as false
        //Add your code to call apex method or do some processing
        this.isModalOpen = false;
    }
}

accountContactsDisplay.js-meta.xml

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

accountContactsDisplay.css

body{
    color: #000;
    padding-left: 200px;
}
th {
    text-align: center;
}
td{
    text-align: center;
}
.container{
    display: flex;
    justify-content: center;
}
table{
    width: 50%;
}
.modalTable{
    width: 100%;
}
.viewBtn{
    padding-bottom:10px;
}

Important Considerations

  • Enable Lightning Web Security: This is a mandatory requirement for dynamic imports.

  • API Version: Ensure your component configuration file has apiVersion set to 55.0 or later.

  • Evaluate Trade-offs: While dynamic imports offer benefits, they can introduce additional complexity and potential performance overhead at runtime. Consider your specific needs before implementing this technique.


Conclusion

To conclude our exploration, the journey into dynamic Lightning Web Components (LWC) heralds a transformative era for Salesforce development. By dynamically importing and instantiating components, developers can craft applications that are performance-optimized and deeply personalized, meeting the ever-evolving demands of users with grace. This innovative approach marks a significant leap forward, promising a future where Salesforce applications are more interactive, responsive, and tailored than ever before. The possibilities are limitless, and the future is dynamically bright.


If you'd like to see the code and resources used in this project, you can access the repository on GitHub.To access the AVENOIRBLOGS repository, click here. Feel free to explore the code and use it as a project reference.


Thank You! You can leave a comment to help me understand how the blog helped you. If you need further assistance, please get in touch with us. You can click "Reach Us" on the website and share the issue with me.


Reference



Blog Credit:

S. Chaudhary

S.Kumar

   Salesforce Developer

   Avenoir Technologies Pvt. Ltd.

  Reach us: team@avenoir.ai

Comments


© 2024 by Avenoir Technologies Pvt. Ltd.

bottom of page