Andreas Dolk
Andreas Dolk

Reputation: 114817

Events emitted from slotted components

I try to wrap an application into a 'Repository' component and that outer component should handle all communication with the backend, like loading, updating, deleting editable data. I thought I could just send events up to that Repository component but I doesn't work the way I do it. Does anyone spot the issue or can explain, why this doesn't work and how to do it correctly with events? I could use writables instead but events would make it more readable. Here's a simplified example and a svelte REPL link:

https://svelte.dev/repl/e1eae56c7d5e48b2a99299f1bc1bf970?version=3.22.3

App.svelte:

<script>
    import Repository from './Repository.svelte'
    import Application from './Application.svelte'
</script>

<Repository>
    <Application on:save={() => console.log('caught in App')} />
</Repository>

Repository.svelte:

<div on:save={() => console.log('caught in Repository')}>
    <slot></slot>
</div>

Application.svelte:

<script>
    import {createEventDispatcher} from 'svelte'

    const dispatch = createEventDispatcher();

    function saveHandler() {
        console.log('dispatching')
        dispatch('save')
    }

</script>

<button on:click={saveHandler}>
    Save
</button>

The desired output would be

dispatching
caught in Repository

but it only prints

dispatching
caught in App

when the button is clicked.

Upvotes: 5

Views: 4507

Answers (2)

Falco
Falco

Reputation: 3436

Svelte Events do not Bubble but HTML Events do

The cleanest solution I have found is to use HTML-Events instead of Svelte dispatched events. HTML Events can bubble up through the DOM and can be catched by parent components.

On the child:

node.dispatchEvent(new CustomEvent('save', { bubbles: true }))

On the parent:

node.addEventListener('save', saveHandler)

Svelte REPL with example: https://svelte.dev/repl/b5f5e41bae704e6a83fc0cf8c0f58f57

Upvotes: 3

Andreas Dolk
Andreas Dolk

Reputation: 114817

Found at least one solution, the let directive can help, although it's no pure eventing and pretty verbose.

https://svelte.dev/repl/a03214d522cd4bcba67f86c426a3b28d?version=3.22.3

App.svelte:

<script>
    import Repository from './Repository.svelte'
    import Application from './Application.svelte'
</script>

<Repository let:saveHandler={saveHandler}>
    <Application on:save={saveHandler} />
</Repository>

Repository.svelte:

<script>
    function saveHandler() {
        console.log('caught in Repository')
    }
</script>

<div>
    <slot {saveHandler}></slot>
</div>

Application.svelte:

<script>
    import {createEventDispatcher} from 'svelte'

    const dispatch = createEventDispatcher();

    function saveHandler() {
        console.log('dispatching')
        dispatch('save')
    }

</script>

<button on:click={saveHandler}>
    Save
</button>

The con (or pro) is that we have to expose all methods from the Repository with let directives on the main component and also catch the events there, which can be a lot of unwanted event forwarding (because events only bubble up one level).

Upvotes: 9

Related Questions