Launch Dynamic LWC in Reusable lightning-modal

Launch Reusable lightning-modal LWC, with a dynamically instantiated LWC…from Aura Component (optionally)


Related References:


Background

The ability to create LWC dynamically was introduced in the Winter ’24 release and allows for greater flexibility in managing components. This post is a bit of an extension from a prior exploration around launching an LWC lightning-modal with embedded Flow from an Aura component. For the purposes of demonstrating the capabilities of dynamic LWC instantiation, I’ve followed the same rough use case template, but with new components. That said, you could easily combine this with the Flow example to have a single and light way to manage a host of LWC/Flows in the same location. For this example, we are calling an LWC dynamically within a lightning-modal from a button click on an Aura component. The selected button determines which LWC is instantiated at run-time within the modal.


Create a Couple of Dummy LWC

First we’ll create a couple of dummy LWC that we can use to prove out dynamic instantiation. I’ve done an ‘demoLWCForDynamicLWC’ and ‘demoLWCForDynamicLWC2’ much like the below.

<template>
    <p>Test Component 1:  {recordId}</p>
</template>

import { LightningElement, api } from 'lwc';

export default class DemoLWCForDynamicLWC extends LightningElement {
    @api recordId;
}


Create an LWC to host the Dynamic LWC(s) within lightning-modal

Now we’ll create the container into which we will instantiate the dynamic LWCs created above, and we’ll do that inside of lightning-modal. The steps below are key to successfully using dynamic LWC. Additionally, see the documentation around dynamic LWC for further details on capability – here I have included a simple working example to demonstrate.

  1. Enable Lightning Web Security in Setup => Session Settings. Note that for existing orgs you must be very careful here to test and adjust existing components. See the guide.
  2. Metadata file for LWC must use capability of ‘lightning__dynamicComponent’ as shown below

The dynamic LWC is done using lwc:component tags with lwc:is being the component that we want to instantiate (dynamicComp in this example).

<template>
    <template lwc:if={showHeader}>
        <lightning-modal-header label={header}></lightning-modal-header>
    </template>
    <lightning-modal-body>
        <template lwc:if={showModal}>
            <div class="container">
                <lwc:component 
                    lwc:is={dynamicComp}
                    lwc:ref="dynamicComp"
                    record-id={recordId}
                >
                </lwc:component>
            </div>
        </template>
    </lightning-modal-body>
    <template lwc:if={showFooter}>
        <lightning-modal-footer>
            <lightning-button label="Close" onclick={handleClose}></lightning-button>
        </lightning-modal-footer>
    </template>
</template>

The Javascript file imports the LWC passed to ‘lwcToImport’ property here, which we will set in the next step. The async connectedCallback is responsible for importing the concrete LWC that will be set in the lwc:component.

import { api } from 'lwc';
import LightningModal from 'lightning/modal';

export default class DemoModalForDynamicLWC extends LightningModal {
    
    dynamicComp;

    @api recordId;
    @api lwcToImport;
    @api showModal = false;
    @api showFooter = false;
    @api showHeader = false;

    @api async connectedCallback(){
        console.log('in connected callback - component to load: '+this.lwcToImport);
        const { default: ctor } = await import(this.lwcToImport);
        this.dynamicComp = ctor;
        this.showModal = true;
    }

    //example from salesforce docs on accessing dynamic component.
    renderedCallback() {
        if(this.refs.dynamicComp){
            //console.log(this.refs.dynamicComp);
        }
    }

    handleClose(){
        this.close('modal is closed');
    }

}

In the xml file we see the aforementioned capability tag including ‘lightning__dynamicComponent’, which is required for this to function.

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

Important Note: If you are using VS Code, you will get an error that says the following. If you have LWS enabled, this error will not be valid, and deployment to org will work fine. If you do not, then you will receive this error on attempted deployment.


Create/Adjust Component to Interact with the Generic Modal

There will be multiple ways to do this, but so far I’ve found it effective to use an independent LWC that dispatches to the Lightning Modal component, and passes the required information to instantiate the dynamic LWC (or Flow from the previous example) when that modal is opened. This component is the only one that will be referenced on the Aura Component in this example, and it will have different functions that manage the logic depending on the specific use case. In this case, we’ll go with an empty template, and import the previously created modal component called ‘demoModalForDynamicLWC’. We will also expose the function ‘testModal’ via @api, which will allow us to call it from the Aura Component with the name of the dynamic LWC to be launched inside the modal — passing to ‘lwcToImport’ so the modal knows which dynamic LWC to create based on which button was pressed. Again, see the documentation as there are a host of was to accomplish this, and I’m just using the simplest of examples here.

import { LightningElement, api } from 'lwc';
import demoModal from 'c/demoModalForDynamicLWC';

export default class DemoLauncherLWC extends LightningElement {

    /* STANDARD ATTRIBUTES */
    @api recordId;

    /* MODAL ATTRIBUTES */
    lwcToImport;
    size = 'large';
    showFooter = false;
    showHeader = false;

    //Test MODAL Launch for Demo Dynamic LWC.
    @api testModal(lwcName){
        this.size = 'full';
        this.showHeader = false;
        this.showFooter = true;
        this.lwcToImport = lwcName;
        this.handleLWCModal();
    }

    /* LWC MODAL OPENING */
    async handleLWCModal() {
        this.result = await demoModal.open({
            size: this.size,
            showHeader: this.showHeader,
            showFooter: this.showFooter,
            lwcToImport:  this.lwcToImport,
            recordId: this.recordId
        });
    }

}


Aura Component From Which to Launch Component

As with the dynamic Flow example, we will use an Aura component to launch the LWC. Hopefully you’ll only be doing so from an LWC, but this will help if you’re stuck with legacy Aura needing to interact more flexibly with LWC. Here we will have 2 buttons- one to instantiate the first dummy LWC, and the other to instantiate the second dummy LWC.

<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId,force:appHostable">
    <aura:attribute name="recordId" type="Id"/>
    <c:demoLauncherLWC 
        recordId ="{!v.recordId}" 
        aura:id="testLWC">
    </c:demoLauncherLWC> 
    <lightning:card title="Demo Aura Component for Dynamic LWC">
        <lightning:button
            variant="brand"
            label="Test Dynamic LWC - 1"
            onclick="{!c.openTestLWC1}"
            class="slds-m-around_x-small">
        </lightning:button>
        <lightning:button
            variant="brand"
            label="Test Dynamic LWC - 2"
            onclick="{!c.openTestLWC2}"
            class="slds-m-around_x-small">
        </lightning:button>
    </lightning:card>
</aura:component>

({
    openTestLWC1 : function(component, event, helper) {
        let testLWC = component.find("testLWC");
        testLWC.testModal("c/demoLWCForDynamicLWC");
    },

    openTestLWC2 : function(component, event, helper) {
        let testLWC = component.find("testLWC");
        testLWC.testModal("c/demoLWCForDynamicLWC2");
    }
})

Trying it Out

After adding the Aura component to the Lightning Record Page, we’re ready to check out how it works. When I click the button on the Aura Component, it targets the ‘demoLauncherLWC’ component and passes the LWC to be dynamically instantiated (via the lwcName on function ‘testModal’). The launcher component then calls the ‘demoModalForDynamicLWC’ lightning-modal component, along with the LWC to be created, and the modal is opened with that LWC included. If we click the other button, a different LWC is instantiated instead, which is as we desire/expect. See the two different LWCs ‘Test Component 1’ vs. ‘Test Component 2’ via the screen capture below. Looks good!


Wrapping Up

Thanks for reading, and hopefully this approach helps in your solution design and execution!