Home > Net >  Svelte dynamic multiple component array
Svelte dynamic multiple component array

Time:06-12

EDIT: REPL for answer https://svelte.dev/repl/eb7616fd162a4829b14a778c3d1627e4?version=3.48.0

What I'm talking would render this:

First we have a button:

<button on:click={add}> Add </button>
<div id="columnflexbox">

When I click it, this should happen:

<button on:click={add}> Add </button>
<!-- This exists in DOM-->
<div id="columnflexbox">

<!-- This div and components get rendered, the Div does not exist in DOM -->
<div  style="display:flex;flex-direction:row">
comp1 (instance1) 
comp2 (instance1)
</div>

When I click it again, another row gets added (with new instances): Add

<div id="columnflexbox">


<div  style="display:flex;flex-direction:row">
comp1 (instance1) 
comp2 (instance1)
</div>
<!-- Second div is generated, it does not exist in DOM, new instances of components -->
<div  style="display:flex;flex-direction:row">
comp1 (instance2)
comp2 (instance2)
</div>


</div>

There is an interface:

export interface complexObject {
    comp1 : ComplexObj1
    comp2: ComplexObj2
}

let row: complexObject[] = []

Add function:

function add(){        
    let newObj:complexObject = {
        comp1: new Comp1({target: div}), // I have to add a target here, or I get compile error, but how since the element doesn't exist?
        comp2: new Comp2({target: div})
    }

    row.push(newObj);
}

I would use this way of doing it, but I can't since I'm getting compile error without adding target:

{#each row as rowitem }

    <div >
        {rowitem.comp1}
        {rowitem.comp2}
    </div>
    
{/each}

Edit: It turns out that the styling gets applied correctly when I add the target as the html specified in the render as so:

Add function:

function add(){    
    let div = document.createElement("div");
    div.setAttribute('class',"row")


    let newObj:complexObject = {
        comp1: new Comp1({target: div}),
        comp2: new Comp2({target: div})
    }

    row.push(newObj);
}


{#each row as rowitem }
<div >
        {rowitem.comp1}
        {rowitem.comp2}
</div>    
        

{/each}

Now the problem is that the components do not get rendered, what I get instead is

[object Object]     [object Object]

When I try to render as such:

  <svelte:component this={rowitem.comp1}>

    </svelte:component>

I get error:

TypeError: l is not a constructor

If I try to modify Add function and remove new keyword I get this:

Type 'typeof comp1__SvelteComponent_' is missing the following properties from type 'comp1__SvelteComponent_': $$prop_def, $$events_def, $$slot_def, $on, and 5 more.ts(2740)

Turns out that this is an IDE problem and it does actually render correctly in the browser.

CodePudding user response:

For this, one would usually use svelte:component which uses the constructor of the component as input and any props one may want to pass along. e.g.

<script>
    import Comp from './Comp.svelte';
    
    let components = [
        [Comp, { content: 'Initial' }],
        [Comp, { content: 'Initial 2' }],
    ];
    function add(component, props) {
        components = [...components, [component, props]];
    }
</script>

<button type=button on:click={() => add(Comp, { content: 'Added' })}>
    Add
</button>

{#each components as [component, props]}
    <svelte:component this={component} {...props}>
        (Slotted content)
    </svelte:component>
{/each}

REPL example


Example with types:

<script lang="ts">
    import type { SvelteComponent, SvelteComponentTyped } from 'svelte';
    import Comp from './Comp.svelte';
    import Comp2 from './Comp2.svelte';
    
    let components: [typeof SvelteComponent, Record<string, any>][] = [
        [Comp, { content: 'Initial' }],
        [Comp2, { color: 'blue' }],
    ];
    function add<T extends typeof SvelteComponentTyped<P, any, any>, P>(
        component: T,
        props: P
    ) {
        components = [...components, [component, props]];
    }
</script>

add enforces the consistency between the component and its props, the array cannot, at least when using tuples. If each item were a class, the class could enforce its internal type consistency.

CodePudding user response:

If you need a reference to a DOM element, you can either query it inside onMount or set a reference to a variable via bind:this (then as well defined/accessible inside onMount) REPL

<script>
    import {onMount} from 'svelte'
    import Comp from './Comp.svelte'
    let container
    
    onMount(() => {
        const containerRef = document.getElementById('container')
        console.log(containerRef, container)
        const c = new Comp({target: container})
    })
</script>

<div id="container" bind:this={container} />

(You query getElementByID but your outer element has and do you iterate newObjs or row? Because first is an object and can't be directly iterated I think...)

  • Related