Skip to content

Instantly share code, notes, and snippets.

@domchristie
Created February 20, 2022 18:17
Show Gist options
  • Save domchristie/59dfd1e8acd45945116652f9fc59f484 to your computer and use it in GitHub Desktop.
Save domchristie/59dfd1e8acd45945116652f9fc59f484 to your computer and use it in GitHub Desktop.

Svelte Model

A factory for creating models that encapsulate Svelte stores. Stores can be automatically subscribed to, with their values accessible from an attrs object.

Here's a contrived example to demonstrate the API. A Pattern is a model that can record events, and schedule them to replay on repeat. The inner workings don't really matter here, but notice that active, recordable, and duration are referenced on attrs.

import { writable, derived } from 'svelte/store'

const Pattern = Model(function ({ stores, store, attrs, destroy }) {
  return function Pattern ({ bpm, scheduler }) {
    let currentPlay

    // stores
    const recordable = store('recordable', writable(false))
    const playing = store('playing', writable(false))
    const length = store('length', writable(8))
    
    store('active', derived([recordable, playing],
      ([$recordable, $playing]) => $recordable || $playing
    ))
    store('duration', derived([bpm, length],
      ([$bpm, $length]) => (60 / $bpm) * $length
    ))

    function toggle () {
      if (attrs.active) {
        recordable.set(false)
        playing.set(false)
        currentPlay?.clear()
      } else {
        recordable.set(true)
      }
    }
    
    function record (fn) {
      const { recordable, duration } = attrs
      currentPlay = recordable && scheduler.repeat({ fn, every: duration })
    }
    
    function play () {
      recordable.set(false)
      playing.set(true)
    }
    
    return { ...stores, toggle, play, record, destroy }
  }
})

const pattern = Pattern({ bpm: 120, scheduler })
pattern.toggle()
pattern.record()
pattern.active // derived store

Why?

As demonstrated above, it's often useful to reference raw store values. Manually subscribing to each of these feels awkward. Another option would be to use get(store) but as the Svelte docs mention:

This works by creating a subscription, reading the value, then unsubscribing. It's therefore not recommended in hot code paths.

So this approach provides a conventional way to make store values accessible inside a model.

Code

export function Model (definition) {
  const stores = {}
  const attrs = {}
  const unsubscribes = []

  function store (name, store) {
    stores[name] = store
    unsubscribes.push(store.subscribe(value => attrs[name] = value))
    return store
  }

  function destroy () {
    while(unsubscribes.length) {
      unsubscribes.shift()()
    }
  }

  return definition({ stores, store, attrs, destroy })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment