I have taught tutorials about multiple props to component, but I want to use it in +layout
.
I hope to insert multiple different components into the specified {@render}
of the upper layout (+layout.svelte
, not a component) at different routing sub-levels (some +page.svelte
files) as like as svelte4 multi named slot.
I currently use the following code to solve the needs, but I intuitively think that my way is bad and very circuitous, and I do not need to dynamically change the inserted components after loading the page. Now I use additional code to pass these snippets via context methods, is there a better way to simplify them?
<!-- +layout.svelte -->
<script lang="ts">
import { setContext, createRawSnippet } from 'svelte';
import { writable } from 'svelte/store';
const dummySnippet = (text: string) =>
createRawSnippet(() => {
return { render: () => text };
});
let { children } = $props();
let slotLeft = writable(dummySnippet('LEFT'));
let slotCenter = writable(dummySnippet('CENTER'));
let slotRight = writable(dummySnippet('RIGHT'));
setContext('LayoutSlot', { slotLeft, slotCenter, slotRight });
</script>
<winbox class="flex flex-row h-full w-full overflow-hidden">
<listbox class="w-[400px]">
{@render $slotLeft()}
</listbox>
<div class="flex-1 flex flex-col border-x">
{@render $slotCenter()}
</div>
<div class="w-[350px]">
{@render $slotRight()}
</div>
</winbox>
{@render children()}
<!-- +page.svelte -->
<script lang="ts">
import { onMount, getContext, type Snippet } from 'svelte';
import type { Writable } from 'svelte/store';
let {
slotLeft,
slotCenter,
slotRight
}: {
slotLeft: Writable<Snippet>;
slotCenter: Writable<Snippet>;
slotRight: Writable<Snippet>;
} = getContext('LayoutSlot');
onMount(() => {
$slotLeft = menuLeft;
$slotCenter = mainContent;
$slotRight = menuRight;
});
</script>
{#snippet menuLeft()}
<p>Left Menu Pending</p>
{/snippet}
{#snippet mainContent()}
<p>Center Content Pending</p>
{/snippet}
{#snippet menuRight()}
<p>Right Menu Pending</p>
{/snippet}
<children>TEST</children>
<!-- .... other option snippets from other routing-->
<!-- {#snippet foo1()} -->
<!-- {#snippet foo2()} -->
Hopefully someone can give me idea for better than my current approach to simplify the codes, maybe there should be a direct way to pass props (not only default children
props) in the +page.svelte
inside html area, but I can't find it.
As of now this is not supported (issue tracking the topic).
Before Svelte 5 you could not do this at all and this is rather hacky. It does not work properly with server-side rendering because the rendering of the layout finishes before the page can send content back up.
There is a way to work around this by running the page first, not rendering anything in it, then render the slots from the context. (Found by zyxd on GitHub.)
E.g.
<!-- +layout.svelte -->
<script>
import { setContext } from 'svelte';
const { children } = $props();
const slots = {};
setContext('layout-slots', slots);
</script>
<!--
Run page logic first, this should not output anything
and only set slots in the context.
-->
{@render children()}
<div class="box">
<div class="left">
{@render slots.left()}
</div>
<div class="right">
{@render slots.right()}
</div>
</div>
{@render slots.children()}
<!-- +page.svelte -->
<script>
import { getContext } from 'svelte';
// This block could be extracted to a function taking the slots
// as arguments for convenience.
const slots = getContext('layout-slots');
Object.assign(slots, { left, right, children });
</script>
{#snippet left()}
<div>Left slot</div>
{/snippet}
{#snippet right()}
<div>Right slot</div>
{/snippet}
{#snippet children()}
<div>Children slot</div>
{/snippet}
Alternatively, create a component with the necessary slots and use that in every page instead of doing this via the +layout.svelte
.