Search code examples
javascripttypescriptsvelte

Can a Svelte component dipsatch its own instance?


For nested Svlete components, where an Outer contains several Inners, is it possible to create a dispatcher with createEventDispatcher in Inner that would communicate the instance that called the dispatcher?

Outer.svelte would look something like

<script lang="ts">
  import Inner from "./Inner.svelte";

  /* placeholder array of 0-10 elements, to make it clear the number of Inners
   * in this component is different depedning on some outside factors */
  let dynamicContent = Array.from({length: Math.random() * 10}, () => "something");

  // How is it possible to set this?
  let lastSelectedInner: Inner | null = null;
</script>

{#each dynamicContent as item}
  <Inner on:select={(event) => lastSelectedInner = event.detail} />
{/each}

And Inner.svelte would look something like:

<script lang="ts>
  import { createEventDispatcher } from "svelte";
  const dispatch = createEventDispatcher();
</script>

<button on:click={() => dispatch("select", /* how do I forward the instance here? */)} >
  <p>Click me</p>
</button>

Note: I need the actual instance to be in a variable, and not an index. Also, I don't want to have an array of all Inners, just a single reference to a single component.


Solution

  • After several solutions with good workarounds were proposed, I was looking into futher options that would fully satisfy the requirement of "no array of inners" and "communicate actual component reference".

    I have come across a Svelte REPL (not sure who the author is) that shows how to make this possible using the special <svelte:self> element. Here is the solution outlined there, adapted to this question. Inner.svelte:

    <script lang="ts">
      export let outer = true;
      export let component = null;
    
      import { createEventDispatcher } from "svelte";
      const dispatch = createEventDispatcher();
    </script>
    
    {#if outer}
      <svelte:self 
        bind:this={component}
        outer={false}
        on:select={() => dispatch("select", component)}
      />
    {/if}
    
    {#if !outer}
      <button on:click={() => dispatch("select")} >
        <p>Click me</p>
      </button>
    {/if}
    

    This approach isn't the most readable, as it requires nesting the inner component within itself and conditionally showing content only in the nested component. However, it achieves precisely the result of being able to dispatch an event from a component, that in its detail communicates a single correctly-typed reference.

    Edit: Updated according to comment from @Corrl