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; }}/>
You can use $on
to 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
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With