Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Svelte v3 programmatically create a component with props and event listeners

Is it possible to create a component and programmatically attach event listeners to it?

I know that this is easily possible for props using <svelte:component/> by spreading with { ...props }. I wonder if something similar can be achieved to attach event listeners.

E.g., in the following example I would like to programmatically attach on:message to A and on:count to B:

<!-- App.svelte -->
<script>
    import A from './A.svelte';
    import B from './B.svelte';

    let message = 'Hi there 👋';
    let count = 0;

    const components = [{
        component: A,
        props: { message },
        listeners: { message: (m) => { console.log(`They say "${m}"`); } }
    }, {
        component: B,
        props: { count },
        listeners: { click: () => { count++; } }
    }];
</script>

{#each components as component}
    <div><svelte:component this={component.component} { ...component.props }/></div>
{/each}

<div>
    <p>They say "{message}"!</p>
    <p>They clicked {count} times!</p>
</div>

<!-- A.svelte -->
<script>
    import { createEventDispatcher } from 'svelte';
    const dispatch = createEventDispatcher();
    export let message = '';
    function changeHandler(e) { dispatch('message', message); }
</script>

<input on:change={changeHandler} on:value={message} value={message} />

<!-- B.svelte -->
<script>
    import { createEventDispatcher } from 'svelte';
    const dispatch = createEventDispatcher();
    export let count = 0;
    function clickHandler() { dispatch('count', count); }
</script>

<button on:click={clickHandler}>Click me</button>

Here's a live demo: https://svelte.dev/repl/af1bd30ab75b43f19b72a306340b7282?version=3.18.2

I.e., I am hoping there's a way to expand the components array to

<A message={message} on:message={e => { message = e.detail; }}/>
<B count={count} on:count={e => { count = e.detail; }}/>
like image 782
F Lekschas Avatar asked Jan 26 '23 07:01

F Lekschas


2 Answers

You can use $onto dynamically register a listener on component events.

In your usecase, you can use this component :

ComponentEvent.svelte

<svelte:component this={component.component} { ...component.props } bind:this={instance}/>

<script>
  export let component;

    let instance;
    
    $: if (instance && component.listeners) {
        for (let [key, listener] of Object.entries(component.listeners)) {
            instance.$on(key, listener);
        }
    }
</script>

and replace in you App.svelte the each loop with :

{#each components as component}
    <ComponentEvent {component}/>
{/each}

see this REPL

like image 191
Jérémie B Avatar answered Jan 27 '23 22:01

Jérémie B


You can't dynamically attach listeners, no - don't forget Svelte is a compiled language, it does all the heavy lifting at compile time, so things need to be known in advance.

What I've done for dynamic listeners is to fire a single known event, and then have the event detail contain differential logic, such as event names, etc. As in the following:

// SomeComponent.svelte
<script>
  import { createEventDispatcher } from 'svelte'

  const dispatch = createEventDispatcher()

  dispatch('component-event', { name: 'alert', value: 'oh noes' })
  dispatch('component-event', { name: 'log', value: 'some message' })
</script>
// App.svelte
<svelte:component this={someComponent} on:component-event={e => handleEvent(e)} />

<script>
  function handleEvent ({ detail }) {
    const { name, value } = detail
    if (name === 'alert') { alert(value) }
    if (name === 'log') { console.log(value) }
  }
</script>
like image 33
Antony Jones Avatar answered Jan 27 '23 21:01

Antony Jones