33

Is there any way to create and add lightning web components dynamically in the component tree ? I mean the equivalent one in Aura is $A.createComponent().

The usual Web Components can be dynamically created through standard DOM APIs like document.createElement('c-my-cmp') or element.appendChild(element), but as far as I tried in pre-release org it would not work at all.

1

6 Answers 6

23

This question has been posted multiple times in the pilot group. The short answer is there is no equivalent for the $A.createComponent() API currently in the LWC .

Currently, the team recommends the below options

  1. Create if branches on the template only load them when a certain condition is meet (yes this is no ideal and not what you are looking for).

  2. You can have multiple html templates in your component. You can have a render() method that can switch which template to use based on some conditions.

Looks like option 2 seems a more viable approach.

Below is the example is shown for this use case

import { LightningElement } from "lwc";

import loadingTemplate from "./loading.html";
import finalTemplate from "./fancyTemplate.html";

export default class HelloWorld extends LightningElement {
@track isLoading = false;

  render() {
     return this.isLoading ? loadingTemplate : finalTemplate;
   }
}

Providing true dynamic creation seems to be technically challenging as per the conversation in the pilot chatter group.

Update

Although there is nothing like this on the salesforce platform today, if you are using lwc-oss on say platform like Heroku or on your website, you can achieve the dynamic component insertion.

Please check an example of the usage here.

13
  • The TLDR here is that this is much, much, much more performant.
    – joshbirk
    Dec 17, 2018 at 16:54
  • 20
    Neither of the above is helping. I'm currently seeking a possibility to bring a React app to LWC, which is dynamically rendering DOMs via react-dom. When I heard about LWC first, I thought it would become possible as it is based on the standard (custom elements). Additionally I also heard someone is saying "LWC components are 'first-class' DOM elements". If we cannot create components through the DOM API, we should stop the applause.
    – stomita
    Dec 18, 2018 at 4:02
  • I think you should present what you need to LWC team and see what's in roadmap .I am sure since it's start there will be plans to provide something along those lines . Dec 18, 2018 at 4:05
  • @joshbirk What is more performant than what?
    – kamezaburo
    Aug 17, 2020 at 1:56
  • 1
    @Scott for lwc-oss you can do dynamic component creation! Check out lwc-oss sample gallery app on trailheadapps github handle Oct 23, 2020 at 20:25
12

Edit: After testing this in a preview scratch org, I get the error:

LWC1518: Invalid LWC imported identifier "createElement"

It looks like they have very intentionally prevented dynamic component creation which is quite disappointing and makes LWC in it's current state significantly less flexible than aura components. I'll leave the rest of my post in the hopes that createElement returns one day.


I'm quite interested in this ability as well as it's something I've seen used heavily within lightning components (now aura components).

I played around with it in the playground and there does seem to be decent support for this.

The playground gives you main.js which contains the following code by default:

// This is the main entry point to the playground. By default,
// it simply creates a single lightning web component, and adds
// it to the DOM tree.

import * as Engine from 'lwc';
import App from 'c-app';

const element = Engine.createElement('c-app', { is: App });
document.body.appendChild(element);

Based off this (the last two lines) I was able to:

  1. Create a component, c-example which exports the class Example that does nothing except render a div with some text using an @api field called name
  2. Create a component, c-renderer which has two @api fields, component-name which is the string name of the class (c-example) and component-class which is a reference to the class (Example)
  3. Render an element of the class dynamically using the code from main.js
  4. Alter the Example's name field directly through JavaScript

The point of this was not to create something useful (this is a component that really does nothing when you think about it) but to test the ease with which you can not only render components but render them without knowledge of what component you're rendering. I think this is a key feature for building heavily dynamic components. It also tests the ability to pass data to these components after rendering them which is also key.

The full playground is available here.

The gist of the code is:

example.js

import { LightningElement, api } from 'lwc';

export default class Example extends LightningElement {
    @api
    name = 'default';
}

example.html

<template>
    <div>
        This is dynamically rendered content. Its name is {name}
    </div>
</template>

renderer.js

import { LightningElement, api, createElement } from 'lwc';

export default class Renderer extends LightningElement {
    _componentName;

    @api get componentName() {
        this._componentName;
    }

    set componentName(value) {
        this._componentName = value;
        this.renderContent();
    }

    _componentClass;

    @api get componentClass() {
        this._componentClass;
    }

    set componentClass(value) {
        this._componentClass = value;
        this.renderContent();
    }

    renderContent() {
        if (!this._componentClass || !this._componentName) return;

        const element = createElement(
            this._componentName, 
            { is: this._componentClass }
        );

        element.name = 'not default';

        document.body.appendChild(element);
    }
}

renderer.html

<template>
    <div>
        This is statically rendered content
    </div>
</template>

app.js

import { LightningElement, track } from 'lwc';
import Example from 'c-example';

export default class App extends LightningElement {
    // I could not reference Example directly. It had to be in a variable.
    componentClass = Example;
}

app.html

<template>
     <c-renderer component-name="c-example" component-class={componentClass}></c-renderer>
</template>
2
  • I have tried it myself, unfortunately, I am thinking this only works in playerground. Trying to deploy to sandbox will throw below exception: force-app/main/default/lwc/helloworldParent/helloworldParent.js [Line: 1, Col: 27] LWC1518: Invalid LWC imported identifier "createElement"
    – Wayne Ni
    Dec 17, 2019 at 3:15
  • Note that saved playground projects are disappearing.
    – Phil W
    Sep 10, 2020 at 7:03
10

See Possible Workaround Below!

First, let me give you as the reader some context. (This is important to understand!)

Dynamically created components are currently not supported at the time of writing this answer (research has shown this could be subject to change, and I will try to remember editing this in that event). This is because the createComponent method that is normally inherited from extending HTMLElement class does not exist (undefined) within the scope of the exposed lwc module.

The Salesforce module that is exposed to developers named 'lwc' defines a class in the engine,

class HTMLElementTheGoodPart {
  //all the things Salesforce thinks are "TheGoodParts" to expose to developers.
}

Further, the class that extends HTMLElementTheGoodPart is LightningElement, which is what your LWC class extends upon construction.

It's frustrating and disappointing, to say the least. The cruel fact of the matter is that Salesforce plays the role of rule maker and gatekeeper on their platform, and they ultimately decide what "TheGoodParts" are. Are "TheGoodParts" the good parts for us as developers, or the good parts for them? Well, given that almost every web component framework supports the methods defined by web standards and LWC does not, I would tend to agree with the latter. There are so many benefits to be gained from embracing web standards for the platform, so I am confused why they omit these standard API features. Wouldn't it be great to have access to reusable web components hosted on (www.webcomponents.org)? Unfortunately, those who are SF developers do not and likely never will.

What this all boils down to is Lightning Web Components are effectively wrapper classes of native web components. I think SF needs to communicate this to the developer community. Developers on the platform who are new to web components will no doubt mistakenly google search otherwise helpful documentation on web components, only to find upon implementation that it isn't supported in LWC.

Possible Workaround for your use case

1) Put your desired dynamically created component in a template with a for:each and for:item directive:

<!-- exampleComponent.html -->
<template for:each={dataForDynamicComponentCreation} for:item="item">
  <c-my-cmp 
      key={item.requiredKey}
      attribute-one={item.attributeOne}
      attribute-n={item.attributeN}>
  </c-my-cmp>
</template>

The idea here is that your binding attribute is going to be initialized to an empty array, and thus nothing is going to render during the initial DOM paint because dataForDynamicComponentCreation is an empty list.

// exampleComponent.js
import { LightningElement, track } from 'lwc';

export default class ExampleComponent extends LightningElement {
  @track dataForDynamicComponentCreation = [];

  //mutator method for dataForDynamicComponentCreation property
  //this can be loaded data or data that responds to some event- your choice

  mutatorMethod() {
    //use spread operator (immutable data structure, do not use .push() )
    this.dataForDynamicComponentCreation = [
        ...this.dataForDynamicComponentCreation,
        {
            requiredKey: <anyUniqueValue>,   //maybe use index?
            attributeOne: <yourValueOne>,
            attributeN: <yourValueN>
        }
    ];
  }
}

Again, the properties attributeOne and attributeN can be set a number of ways so I left out implementation on where those are defined. Now, since the property dataForDynamicComponentCreation is being tracked, the DOM will rerender when the bound data property is "mutated". While this is certainly a hack, I think it will serve nicely for simple use-cases and should do the job when you have N number of identical components to "create" dynamically at run-time. Best of luck my SF brethren.

0
3

Winter '24 is adding the lwc:component directive which will allow dynamically rendering components

https://help.salesforce.com/s/articleView?id=release-notes.rn_lwc_directives.htm&release=246&type=5

2

@stomita, LWC currently have no support for the dynamic creation of LWC Components.

But, I guess there is a workaround for this to inject the LWC component using Aura since they co-exist with each other. Here is the work-around I followed

auraWithDynamicLWC.cmp

<aura:component implements="flexipage:availableForAllPageTypes">
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <p>Dynamically created LWC Component</p>
    {!v.body}
</aura:component>

auraWithDynamicLWCController.js

({
    doInit : function(cmp) {
        //Dynamically get the component name
        let dynamicLWCName = 'c:HelloBinding';
        $A.createComponent(
            dynamicLWCName,
            {
                "aura:id": "findableAuraId",
                "greeting": "Dynamic Component",
                "onclick": cmp.getReference("c.handlePress")
            },
            function(newButton, status, errorMessage){
                //Add the new button to the body array
                if (status === "SUCCESS") {
                    var body = cmp.get("v.body");
                    body.push(newButton);
                    cmp.set("v.body", body);
                }
                else if (status === "INCOMPLETE") {
                    console.log("No response from server or client is offline.")
                    // Show offline error
                }
                else if (status === "ERROR") {
                    console.log("Error: " + errorMessage);
                    // Show error message
                }
            }
        );
    },

    handlePress : function(cmp) {
         // Find the button by the aura:id value
         console.log("button: " + cmp.find("findableAuraId"));
         console.log("button pressed");
    }
})
1
  • Using Aura here in the component mix will mean you lose a lot in terms of performance...
    – Phil W
    Nov 12, 2019 at 20:02
1

As of Winter '24, it is now possible to dynamically create LWC components within another LWC. Full details available here: https://developer.salesforce.com/docs/platform/lwc/guide/js-dynamic-components.html

First, you need to add capabilities to your LWC component (js-meta.xml) and also have the API running on version 55 or higher:

<?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>

In the HTML file, you need to have a lwc:component placeholder element for the LWC in question; once the dynamic LWC is created, this component gets replaced:

<template>
    <div class="container">
        <lwc:component lwc:is={componentConstructor}></lwc:component>
    </div>
</template>

Within your Javascript File, you need to dynamically create the LWC

import { LightningElement } from "lwc";
export default class extends LightningElement {
  componentConstructor;
  // Use connectedCallback() on the dynamic component
  // to signal when it's attached to the DOM
  connectedCallback() {
    import("c/concreteComponent")
      .then(({ default: ctor }) => (this.componentConstructor = ctor))
      .catch((err) => console.log("Error importing component"));
  }
} 

Replace "c/concreteComponent" with your LWC name and the placeholder will create the html equivalent <c-concrete-component>

The documentation says that import("c/" + "analyzable") doesn’t work, but I have had success using variables in this slot doing import("c/" + this.lwcName) and had it render correctly, so not sure exactly what was intended for that.

Note: If you are getting an error along the lines of Dynamic Import is not Allowed, you need to go to Setup > Session Settings and enable "Use Lightning Web Security for Lightning web components and Aura components"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .