Svelte's Take on ObservableObject

SwiftUI's ObservableObject and @Published make it easy to group related properties into a single class, and to react to changes of individual properties.

I recently complained to my friend, Bobi, that Svelte seemed lacking in this regard. Its tutorials always show components whose exportable state is defined by separate, top-level variable declarations.

    let count = 0;

    $: if (count >= 10) {
        alert('count is dangerously high!');
        ...

She set me straight: if you want to group related, observable properties, just use a writable store whose value is an Object (or if you prefer, a class instance). Whenever any property is changed, interested parties react automatically.

Here's an example, built from a skeleton project configured to use TypeScript.

Create the Project

$ npm create svelte@latest example

This project will use TypeScript.

✔ Which Svelte app template? › Skeleton project
✔ Add type checking with TypeScript? › Yes, using TypeScript syntax
✔ Add ESLint for code linting? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
✔ Add Playwright for browser testing? … No / Yes
✔ Add Vitest for unit testing? … No / Yes

Add a Store

Add a file, src/lib/field_store.ts:

import { writable } from "svelte/store"

class FieldStore {
    fullname: string = ""
    age: number | null = null
}

export const fields = writable(new FieldStore())

Bind to Store Members

Modify src/routes/+page.svelte to use the store. (Please pardon my dangerous use of unsanitized input values.)

<script lang="ts">
    import { fields } from '$lib/field_store'

    const dfltDispName = '<span class="unknown">I am Spartacus</span>'
    let dispName = dfltDispName
    $: dispName = $fields.fullname || dfltDispName

    const dfltDispAge = '<span class="unknown">not known</span>'
    let dispAge = dfltDispAge
    $: dispAge = $fields.age == null ? dfltDispAge : $fields.age.toString()
</script>
<form>
    <input type="text" placeholder="Full name"
           bind:value={$fields.fullname} />
    <input type="number" min={0} max={4096}
           bind:value={$fields.age} />
</form>

<p>Full name: {@html dispName}.</p>
<p>Age (years): {@html dispAge}.</p>

<style> ... </style>

Profit

Example Svelte Single Page App

Summary

That's it! As usual for Svelte, the syntax is beautifully concise.