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.