Making class instance reactive in Svelte using stores
Asked Answered
D

4

11

I am learning Svelte by creating simple app.

The logic is written using classes. The idea is, that all the data needed comes from class instance properties. Instances should not be instantiated more than once. I am using stores to provide components this instances.

The problem is I can't get reactivity using this approach. I tried readable and writable stores and nothing helps. It is still possible to get reactivity using OOP and what can I do? Reassignment and creating new instances will be expensive.

Edit

I can't make up the example in REPL cause the class is too big.

Parser.js

export default class Parser {
  constructor() {
    this._history = [];
  }

  parse(string) {
    this._history.push(string)
  }

  get history() {
    return this._history;
  }
}

Here I pass instance to the store.

parserStore.js

import writable from "svelte/store";
import Parser from "Parser.js"

export const parserStore = writable(new Parser());

In this component I get the instance and use reactively a method.

Component_1.svelte*

import { parserStore } from "parserStore.js";

$: result = parserStore.parse(binded_input_value);

What I want to get is the up to time history property that was updated from using class method:

Component_2.svelte

import { parserStore } from "parserStore.js";

$: history = parserStore.history;

{#each history as ... }

I know, it is not the best example, but what I want is reactive class instance available through the store. Actually the values are up to date, but it is not causing the re-render of the components. When the component is mounted - data of the latest, but after nothing re-renders at all even so the properties of the instance is changed.

Dread answered 13/7, 2020 at 21:6 Comment(2)
We need to see what you did. Show your code, here, or on a svelte REPL.Grantinaid
@AndreasDolk I tried to provide some sort of example, I hope it will help!Dread
H
17

Short answer

As far as I know you cannot do this, this way.

Longer answer

There might be, depending on some factors (like preferences, existing libraries, etc...), ways around it.

Solution 1: use stores in the class

The first and most straightforward one is to use stores in the class itself:

Parser.js

import { writable } from 'svelte/store'

class Parser {
  constructor() {
    this._history = writable([])
  }

  parse(string) {
        console.log(string)
    this._history.update(v => [...v, string])
  }

  get history() {
    return this._history;
  }
}

parserStore.js

import { Parser } from './Parser.js'¨

export const parser = new Parser()

Component1.svelte

<script>
    import { parser } from './parserStore.js';

    let value
    let { history } = parser
    
    $: parser.parse(value);
</script>

<input bind:value />

{#each $history as h}<p>{h}</p>{/each}

Notice how only the history part of this class would be a store.

Solution 2: Rewrite using Custom Store

This approach is, in essence, very close to the previous one but is slightly more common in the Svelte Community. It technically just wraps the build in stores to get some extra functionality.

parserStore.js

import { writable } from 'svelte/store'

export const parser = (() => {
    const P = writable([])  
    const { set, subscribe, update } = P    
    
    function parse(string) {
        P.update(arr => [...arr, string])
    }
    
    return {
        parse,
        subscribe
    }
})()

Component1.svelte

<script>
    import { parser } from './parserStore.js';

    let value
    $: parser.parse(value)
</script>

<input bind:value />

{#each $parser as h}<p>{h}</p>{/each}

Note that here there is not history property anymore, you iterate straight over parser, if you still want the history property you have to adjust the code slightly:

parserStore.js

  ...
  return {
    parse,
    history: { subscribe }
  }

Component1.svelte

<script>
  ...
  const { history } = parser
  ...
</script>

{#each $history as h}<p>{h}</p>{/each}
Hectometer answered 14/7, 2020 at 5:24 Comment(4)
Oh, I see. Thank you! I guess, I will use the "store as a part of class" approach. I did not think about it, wow!Dread
Now how do you get this._history or P within your class, as it is a writable type and not your actual value at a certain point in time...?Tubule
As put below, other examples of implementations for classes and Svelte stores: gist.github.com/3lpsy/55da83779a50f603a78ae8331e360a37 and "6.7 Using stores with classes" livebook.manning.com/book/svelte-and-sapper-in-action/chapter-6/…Preachment
How would you get items or count of items in the history when it is 'writable' store? history.length does not work anymore. It seem it is not suitable for more performant code. I want to use certain state 'as is' when i call another method. So I only want to ensure that when i call parser.history it has the most recent list of items, without 'notifying me' on changes explicitly (via subscribe)Fibrilliform
M
5

Had the same problem and found a solution how it is possible to use "reactive" classes in svelte.

In svelte anything with a subscribe function is a store. So if you want to make a class to a store you have to implement a subscribe function.

import { writable } from 'svelte/store';

class ClassStore {
  constructor() {
    this._history = writable([])
  }

  parse(string) {
    this._history.update(v => [...v, string])
  }
    
  subscribe(run) {
    return this._history.subscribe(run);
  }
}

export const classStore = new ClassStore();

Here is a working example: https://svelte.dev/repl/8e3f4f664cc14710afce0c9683e04652?version=3.42.6

Musgrave answered 19/9, 2021 at 0:42 Comment(0)
P
2

Note to myself:
Here seems a more complex exemple, with class AND custom store(!):

https://gist.github.com/3lpsy/55da83779a50f603a78ae8331e360a37

Interesting:

/*
I recently jumped into svelte programming and wanted to create a class/singleton that could double as a reactive writable store. 
I did things very wrong until people on the discord confirmed how wrong my implementation was so I wanted to provide a simple example.
So below is a simple account store you can use. I'm not sure if it's optimal or even correct, but it's better than my first attempt. 
Feel free to provide feedback.
*/
...

Edit,
And this:

6.7 Using stores with classes

https://livebook.manning.com/book/svelte-and-sapper-in-action/chapter-6/v-5/106
From the Manning book on Svelte.
The code parts in the link are visible and you can get the whole chapter for free just by creating a new account (if there is a popup that appears).

Divers:
https://devlinduldulao.pro/svelte-in-a-nutshell-with-store-code-sample/ https://medium.com/geekculture/svelte-stores-352c61759a88

Preachment answered 22/1, 2022 at 6:27 Comment(0)
T
1

There is another way that works with adding a writable to the class itself. Add a save() method:

Parser.js

export default class Parser {
  constructor() {
    this._history = [];
  }

  parse(string) {
    this._history.push(string)
  }

  get history() {
    return this._history;
  }
  
  save() {
    return this;
  }
}

Component_1.svelte

import { Parser } from "Parser.js";
import writable from "svelte/store";

// create new class instance
const p = new Parser();

// add some values
p.parse(binded_input_value);

// save that class instance
const parserStore = writable(p.save());

// can be called after page is loaded
function showHistory() {
  console.log(get(parserStore).history);
}

You can keep the value after the page is loaded, you can pass p.save() to a child component, or if component 2 is not a child, you could use setContext() and getContext()...

The point is to save the state of the class at any given moment...

Tubule answered 19/2, 2021 at 18:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.