Using slots inside {@html}

Viewed 540

In my custom component, I had an html string from a 3rd party library (which I have write-access to the source code). I wanted to put <slot></slot> inside the body of that html fragment.

The html string from the 3rd party library might look like:

<div>
    <!-- <slot></slot> is supposed to be here -->
</div>

MyComponent.svelte:

<script>
    $: htmlString = lib.getHtmlFragment(/* optional body html*/);
</script>

{@html htmlString}

<!-- How do I put slots inside the htmlString -->

App.svelte:

<MyComponent>
   <input bind:value />
</MyComponent>

So, how can I put the <slot></slot> wrapped inside the {@html htmlString} without losing the slot's reactivity?

2 Answers

It's not possible as described but that would be pretty cool. We can confirm it fails with dummy data like this REPL.

You can replace a div with a certain ID that is in the dynamic content in onMount with the slotted content, but you lose reactivity like shown in this REPL since it's already compiled after running through the @html block.

Instead of copying the slotted content into the dynamic content, you could set a point to split the dynamic content where you want the slotted content added in and put the rest of the dynamic content after the slotted content, like is show in this REPL. In this example I'm using a keyword to split the HTML string and it only allows for a single slot but it does retain reactivity and allow for a predictable result if the input is controlled by you through the source code and you only need one slot.

Or you can get really wild and create a mini-shortcode template syntax using this same idea by iterating over the segments of a split HTML string and rendering a different component based on the contents of the segment like shown in this REPL. In this example I ditched slots altogether, rendered different components based on the contents of the segment, and controlled the data through a store. With this method you can control props passed down to the components and add as much control as you might need for your content and components directly from the HTML string.

With the last two solutions make sure there's no wrapping parent div tag around the supplied dynamic content. Svelte will always force a single @html tag to be valid HTML on it's own and will close open tags when splitting an HTML string like this which would make your markup unpredictable for CSS/crawling/testing purposes.

Instead of injecting a slot, you can maybe search an injection point at runtime and then create a component here:

<script>
  import { onMount } from 'svelte';
  import MySlotComponent from './MySlotComponent.svelte';

  export let html; // external html
  let el;

  onMount(() => {
    let target = el.querySelector(...);
    const cpn = new MySlotComponent({ target });

    return () => cpn.$destroy();
  });
</script>

<div bind:this={el}>
  {@html html}
</div>
Related