Skip to content

Inline component definitions #7972

@brunnerh

Description

@brunnerh

Describe the problem

Integrating Svelte with certain vanilla JS libraries or components from other systems can be cumbersome due to how only a single component can be defined per file.

Consider e.g. a data table component that allows rendering its cells via callback functions that get passed data which is fetched asynchronously from a server. To use Svelte for rendering the cells, you can define a new component for each cell or bundle all the logic into one component and use an {#if} cascade with some flag that identifies the cell to render.

A hypothetical example:

import NameCell from './NameCell.svelte';
import DescriptionCell from './DescriptionCell.svelte';
import ActionsCell from './ActionsCell.svelte';

function tableAction(node) {
	createDataTable({
		table: node,
		dataSource: '/api/data',
		columns: [
			{ title: 'Name', render: row => renderCell(row, NameCell) },
			{ title: 'Description', render: row => renderCell(row, DescriptionCell) },
			{ title: 'Actions', render: row => renderCell(row, ActionsCell) },
		],
	});
}

function renderCell(row, component) {
	const cell = document.createElement('div');
	new component({ target: cell, props: { row } });

	return cell;
}
<!-- NameCell.svelte -->
<script>
	export let row;
</script>
{row.first} {row.last}
<!-- DescriptionCell.svelte -->
<script>
	export let row;
</script>
{row.description}
<!-- ActionsCell.svelte -->
<script>
	export let row;
</script>
<a href="/user/{row.id}">Details</a>

Describe the proposed solution

Allow defining components within a Svelte file, e.g. by reusing svelte:fragment or another special element:

<script>
	let NameCell, DescriptionCell, ActionsCell;
	
	function tableAction(node) {
		createDataTable({
			table: node,
			dataSource: '/api/data',
			columns: [
				{ title: 'Name', render: row => renderCell(row, NameCell) },
				{ title: 'Description', render: row => renderCell(row, DescriptionCell) },
				{ title: 'Actions', render: row => renderCell(row, ActionsCell) },
			],
		});
	}
	
	function renderCell(row, component) {
		const cell = document.createElement('div');
		new component({ target: cell, props: { row } });
	
		return cell;
	}
</script>

<svelte:fragment bind:this={NameCell} let:row>
	{row.first} {row.last}
</svelte:fragment>
<svelte:fragment bind:this={DescriptionCell} let:row>
	{row.description}
</svelte:fragment>
<svelte:fragment bind:this={ActionsCell} let:row>
	<a href="/user/{row.id}">Details</a>
</svelte:fragment>
  • bind:this would cause the fragment to be compiled to a component that is then assigned to the referenced variable.
  • let bindings are analogous to props (export let ... ) on regular components
  • Events forwarded with on:event could be handled on the component instance via $on and the other API instance functions should work the same as for any component if possible

Ideally it would be possible to also use this to just extract elements locally, e.g. if the hierarchy needs to be dynamic but you do not want to separate common parts into extra files, e.g.:

<script>
	export let data;

	let Content;
	let inList = /* some logic */;
</script>

<svelte:fragment bind:this={Content}>
	<h2>{data.title}</h2>

	<p>Items: {data.length}</p>
	{#each data as item}
		<div>...</div>
	{/each}
</svelte:fragment>

{#if inList}
	<ul>
		<li><svelte:component this={Content} /></li>
	</ul>
{:else}
	<svelte:component this={Content} />
{/if}

Or maybe it would be possible to assign a name so the extra binding and svelte:component becomes unnecessary:

<script>
	export let data;

	let inList = /* some logic */;
</script>

<svelte:fragment name="Content">
	<h2>{data.title}</h2>
	...
</svelte:fragment>

{#if inList}
	<ul>
		<li><Content /></li>
	</ul>
{:else}
	<Content />
{/if}

Alternatives considered

The {#if} cascade in a single component is probably the best current workaround if many separate components would have to be created otherwise.

For the above example that would be something like:

<script>
	export let row;
	export let cell;
</script>

{#if cell == 'name'}
	{row.first} {row.last}
{:else if cell == 'description'}
	{row.description}
{:else if cell == 'action'}
	<a href="/user/{row.id}">Details</a>
{/if}

Importance

would make my life easier

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions