4

I am trying create components dynamically with the help of lwc:dynamic directive in LWC OSS.

I am able to create those comments dynamically, but not able to set the properties to them. For each different component I have different set of properties that I want to pass on to.

Here is the code I am trying.

JS

import { LightningElement } from 'lwc';

export default class CompositionDynamic extends LightningElement {
    componentConstructor;

    async loadComponent() {
        const ctor = await import('recipe/hello');
        // how can I set properties here? For example label,name etc.
        // Like the components i also want to import their default property values dynamically.
        this.componentConstructor = ctor.default; 
    }
}

HTML:

<template>
    <ui-card title="CompositionDynamic">
        <ui-button label="Load Hello Component" onclick={loadComponent}>
        </ui-button>

        <div class="dynamic-component">
            <!-- 
                You can use any tag name for the dynamic component as long as it belongs to your namespace and it's not an existing component in your project.
                For example: In this recipe, we use a "recipe-dynamic-import" tag, but there's no dynamicImport component in the recipe namespace.
            -->
            <recipe-dynamic-import
                lwc:dynamic={componentConstructor}
            ></recipe-dynamic-import>
        </div>

        <recipe-view-source source="recipe/compositionDynamic" slot="footer">
            Load a component dynamically by assigning a constructor at runtime.
        </recipe-view-source>
    </ui-card>
</template>

I have already tried the below code but didn't work

this.componentConstructor.prop = "value";
this.componentConstructor.setAttribute("prop", "value");

2 Answers 2

3

You can pass attributes down to the component as with normal component you would do.

<recipe-dynamic-import lwc:dynamic={componentConstructor} name={name} label={label}>
</recipe-dynamic-import>

The only issue with this approach is that you must have the attributes defined on all of your dynamically imported components. So in the above example, you need to have @api name and @api label in every component.

This can be easily solved by passing down a single attribute called data and have every attribute defined inside this object.

<recipe-dynamic-import lwc:dynamic={componentConstructor} data={data}>
</recipe-dynamic-import>
data;

async loadComponent() {
    if (...) {
        const ctor = await import('recipe/hello');
        this.data = {name: 'hello'};
    } else {
        const ctor = await import('recipe/world');
        this.data = {label: 'wolrd'};
    }
    this.componentConstructor = ctor.default; 
}

This approach has its own drawbacks, nevertheless with this new lwc:spread this should be easily solved, rfc.

7
  • Stumbled upon this rfcs.lwc.dev/rfcs/lwc/0000-lwc-dynamic-with-attrs while I was searching for a bug with my proposed issue. The issue with the above approach is that you need to carefully handle the passed object attributes. If the component dynamically changes, then for some reason the old component got refreshed with the newly assigned data object.
    – Damecek
    May 6 at 20:11
  • Both your question and my issue should be resolved by the rfc
    – Damecek
    May 6 at 20:11
  • +1 Thanks for the Idea, passing data has the limitation that we can only pass string values, so data has to be converted to a string and vice versa. So I used a workaround to query the component using the recipe-dynamic-import selector and then set the public values from JS dynamically based on the type of component. May 9 at 10:04
  • passing data has the limitation that we can only pass string values, this is not true. Currently, I am passing a whole user object directly from Firebase, and logging it in parent and child yields the same object (with booleans, numbers,...)
    – Damecek
    May 12 at 19:19
  • parent: photoURL: "https://lh3.googleusercontent.com/a/AATXAJyHDYgI5qUVPlWH6ih0oNuRwNnevaavSjJeTk5W=s96-c" proactiveRefresh: ProactiveRefresh {user: UserImpl, isRunning: true, timerId: 17, errorBackoff: 30000}
    – Damecek
    May 12 at 19:19
-1

I found the answer and it was very simple. I ended up using setAttribute(). So I put the code to set attributes dynamically based on the type of the components in renderedCallback.

I have a class-level attribute that defines what type of component I am loading dynamically. So based on that I am setting the attributes.

Here is the simplified code snippet from my renderedCallback function.

renderedCallback(){
    let dynamicCmp = this.template.querySelector('recipe-dynamic-import');
    if(dynamicCmp){
        if(this._componentType = 'layout'){
            dynamicCmp.setAttribute('layout-prop-a', this.layoutProp1);
            dynamicCmp.setAttribute('layout-prop-b', this.layoutProp2);
        } else if (this._componentType = 'layout-item'){
            dynamicCmp.setAttribute('layout-item-prop-a', this.layoutItemProp1);
            dynamicCmp.setAttribute('layout-item-prop-b', this.layoutItemProp1);
        } else if(this._componentType = 'grid-import'){
            dynamicCmp.setAttribute('grid-import-prop-a', this.gridImportProp1);
            dynamicCmp.setAttribute('grid-import-prop-b', this.gridImportProp2);
        }
        //...
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.