sveltesvelte-5

Passing children to a snippet


I have a Svelte page like this:

{#await dataPromise}
    <div transition:fade onoutrostart={takeOutOfStaticFlow}>
        <CustomDataLoadingProgressComponent />
    </div>
{:then data}
    <div transition:fade onoutrostart={takeOutOfStaticFlow}>
        <CustomDataTableComponent data={data} />
    </div>
{/await}

I want to extract the loading transition div to avoid code duplication, but I don't want to extract it to a separate component file - my page file is explicitly devoted to managing data loading, I want this content to remain in the file. Is there way to extract it to a snippet in the same page file and to have it accept children markup somehow? The problem is, the {@render} template tag doesn't seem to be able to accept children - it doesn't have a starting and an ending tag, it's just one tag. I can only think of passing children as arguments:

{#snippet contentTransition(children: Snippet)}
    {@render children()}
{/snippet}

But then how do I specify the children argument? Is there a way besides extracting the children content into its own tags?

{#await dataPromise}
    {@render contentTransition(dataLoadingProgress)}
{:then data}
    {@render contentTransition(dataTable)} // How to pass data to dataTable?
{/await}

{#snippet dataLoadingProgress()}
    <CustomDataLoadingProgressComponent />
{/snippet}

{#snippet dataTable(data: Data)}
    <CustomDataTableComponent data={data} />
{/snippet}

But then, as the comment says, how to pass data to the dataTable snippet? Also, I already have the CustomDataLoadingProgressComponent and CustomDataTableComponent as separate units - I don't want to wrap them into snippets, that's just useless noise and duplication again.

Is there anything else I can do here to make contentTransition snippet work as I want it to? Or maybe I should consider something other than snippets? I guess I can construct a props object and do <div {...props}> but that limits me to one HTML element and is not universal.

(Please don't focus on my arguments for not extracting to a separate component file - this question is about exploring Svelte's possibilities.)


Solution

  • If I understand your question correctly, I believe your answer to be something like this:

    {#snippet contentTransition(Component: Component, props?:any{} = {})}
        <div transition:fade onoutrostart={takeOutOfStaticFlow}>    
            <Component {...props}></Component>
        </div>
    {/snippet}
    
    {#await dataPromise}
        {@render contentTransition(CustomDataLoadingProgressComponent)}
    {:then data}
        {@render contentTransition(CustomDataTableComponent, {data: data})}
    {/await}
    

    Don't forget to import type {Component} from 'svelte' . I'm not sure this'll work but based on my past experience working with Components as props and snippet arguments it should (edit: in this REPL it works as expected 😊). The reason you can pass your component and render it like that is because in Svelte 5 Components are no longer classes, and by spreading the props you can basically pass an object with each prop name as a key and it's corresponding value.

    Also, if you'll indulge me, I think it's a very interesting question when it comes to implementing abstractions using Svelte's new features, but you'll notice that this answer literally doesn't save you any lines (and includes an extra import, pottentially adding a new one). I'd argue that for this specific case your code probably more understandable as it is (I'd just select the whole div opening block and ctrl+D the hell out of it to make batch changes instead of going the snippet way.)