This is a trick I came up with a few months ago and have been using regularly. It lets you render a component for a specific object without needing to import the component and without needing to pass the object as a prop. Two very common occurrences!
First, here's the component we want to render:
<span class="na">lang="ts">
import { type Instance } from "$lib/classes/Instance.svelte.js"
let { instance }: { instance: Instance } = $props()
{instance.name}
Now, here's the effect of what we're going to do, both before and after, with the relevant lines marked:
Before:
<span class="na">lang="ts">
import InstanceComponent from './Instance.svelte' // this line
import { Instance } from '$lib/classes/Instance.svelte.js'
const instance = new Instance("Hello there")
{instance} />
After:
<span class="na">lang="ts">
import { Instance } from '$lib/classes/Instance.svelte.js'
const instance = new Instance('General Kenobi')
/>
To make it work, first save this function somewhere in your project:
import { type Component as ComponentType } from "svelte";
import { type ComponentInternals } from 'svelte'
interface Props {
[key: string]: any
}
export function withProps<T extends Props>(Component: ComponentType, defaultProps: T) {
return function ($$anchor: ComponentInternals, $$props: Partial<T>): ReturnType<typeof Component> {
const mergedProps = { ...defaultProps, ...$$props };
return Component($$anchor, mergedProps);
};
}
You don't necessarily need the typescript type imports or interface, but I add it for completeness.
Next, make a class for your object, and add a component getter, like this:
import { withProps } from "$lib/functions/with-props.js"
import InstanceComponent from "$lib/components/Instance.svelte"
export class Instance {
public name = $state('')
constructor(name: string) {
this.name = name
}
// has to be a getter for the component to be reactive
get component() {
return withProps(InstanceComponent, { instance: this })
}
}
That's it! Now you can use Svelte 5's dot notation to write less code! :)
Ok, here's how the withProps
function works. It's pretty straightforward. Let's look at it without the typescript:
export function withProps(Component, defaultProps) { // this line is called when defining get component()
return function ($$anchor, $$props) { // this line is called by the dot notation
const mergedProps = { ...defaultProps, ...$$props }; // anchor is the element to be added to
return Component($$anchor, mergedProps); // this is what is returned to be rendered
};
}
All this function does is take the imported component, and the props you want to add by default (which in this case is the class instance.) We're doing this in the component getter. Then withProps
returns a function that merges the default props with the props that are passed to the component when Svelte needs to render it, i.e. when you use the dot notation, which in this example is the
.
You can of course add more props in the normal way, so say you added a second prop to the component:
let { instance, nonDefaultProp } = $props()
nonDefaultProp="How did this happen?" />
Then you would only need to pass that second prop when calling the component.
You can also override the default prop:
instance={{ name: "override "}} />
That's everything I know about it. It's been working for me for months and I haven't had any issues with it so far. That said, if anyone knows why it's a bad idea, please let me know! :D