'Sveltekit communication between layout, routes and components

I'm trying to move a svelte SPA into Sveltekit.

In my SPA, the communication schema is what I would call a "controller component" which takes care of displaying some components, listen to their events and update the app accordingly. By and large it looks like this REPL:

https://svelte.dev/repl/47bd3f8004624a3c95653b1f1aefd8ee?version=3.46.4

As you can see in this example, the sequence is fairly trivial: App state 1) App shows CompA and CommonComp App state 2) CompA triggers the doSomething function just after being mounted App state 3) App then call commonComp.setTitle and show CompB in place of CompA

In SvelteKit, I struggle to do something similar cause I don't understand how to pass data from a slotted sub component to the Component containing the slot and conversely. Anyway, this led me to this attempt:

  1. I need 2 routes:

    • PageA.svelte for when CompA & CommonComp are displayed
    • PageB.svelte for when CompB & CommonComp are displayed
  2. Because CommonComp is always visible in every states, I would think that it should resides in a __layout.svelte file.

...This took me to the draft below with the comments explaining the access problem I encounter.

/src/routes/__layout.svelte

<slot />
<CommonComp />

/src/routes/PageA.svelte

<script>
  import { goto } from "$app/navigation";
  import CompA from "$lib/CompA.svelte";

  function handleDoSomethingFinished() {
    goto("/test/pageB");
  }
</script>

<CompA on:do_something_finished={handleDoSomethingFinished} />

/src/lib/CompA.svelte

<script>
  import { onMount } from "svelte";
  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  onMount(() => {
    setTimeout(() => dispatch("do_something_finished"), 3000);
  });
</script>

<p>Component A</p>

/src/routes/PageB.svelte

<script>
  import CompB from "$lib/CompB.svelte";

  // How to call CommonComp.setTitle function from here ?
 
</script>

<CompB  />

/src/lib/CompB.svelte

<p>Component B</p>

/src/lib/CommonComp.svelte

<script>
  let title = "Common Component";
  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  export function setTitle(t) {
    title = t;
    dispatch("title-modified");
  }
</script>

<p>{title}</p>

I guess I may have tried to share some stores and check they value into reactive statements to trigger the appropriate actions but when I'm thinking of it, I see a can of worms so I'm missing something here. Thank you for your help.



Solution 1:[1]

This is a little complex. According to the Svelte docs on export:

If you export a const, class or function, it is readonly from outside the component. Function expressions are valid props, however.

Readonly props can be accessed as properties on the element, tied to the component using bind:this syntax.

Meaning that you would have to create a reference to CommonComp using bind:this then pass it around in order for the setTitle method to be called on it. This would be prop drilling down with the added headache of drilling down from a layout's slot. I would not recommend that at all.

One route is to make use of the load facility and stuff, by setting a property like, say, ccTitle on the stuff object from the load function of PageB, then subscribing to the page store provided by $app/stores inside CommonComp to set the title reactively:

$: title = $page.stuff.ccTitle || "Common Component"

however, because you're setting stuff from the load function of PageB, it is not very flexible (load is executed from the module section prior to the page actually loading, so you cannot interact with stuff from the actual page script).

This is a bare bones example of the method above.

In the end, the store route you appear reluctant to go down to would be the simplest and most straightforward option. The title store is basic to the extreme:

import { writable } from 'svelte/store';

const title = writable('Common Component');

export default title;

So is using the store in CommonComp:

<script>
  import title from "$lib/stores/title";
</script>

<p>{$title}</p>

And so is setting a new title from PageB:

<script>
  import CompB from "$lib/CompB.svelte";
  import title from "$lib/stores/title";

  $title = "Custom Title";
  // or say you wanted to set it reactively based on a 'foo' variable:
  // $: $title = foo
</script>

<CompB />

This is miles simpler & cleaner than the load approach mentioned above, as well as a would-be drilling down approach. Stores do not need to be complex and are extraordinarily helpful to communicate across pages & components.

Here is a bare bones example using the stores approach.

Update / edit:

If you wanted to keep your common component methods encapsulated, you could also use the store method mentioned above, but instead of storing the title, you would store the reference to your CommonComp instance, thus providing a means to call $commonComp.setTitle() from any page or component that subscribed to the store.

Here is an example using that approach.

Solution 2:[2]

The structure of components is most important to bind values easier.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Rogier Gorter