Home > Net >  Using ES6 classes, including setters and methods
Using ES6 classes, including setters and methods

Time:07-08

I'm new to programming in Svelte. I would like to be able to use a method on an ES6 class instance in order to dynamically change values being used on my SPA. (Using svelte-spa-router is not an option, unfortunately.)

To get to the crux of the problem in a simplified from:

This is router.js:

import { writable } from 'svelte/store';

export class Router {
    constructor(pageMap) {
        this.pageMap = pageMap;
        this.current = {};
        this.currentName = '';
    }
    
    get name() {
        return this.currentName;
    }
    
    set name(pageName) {
        this.current = this.pageMap[pageName];
        this.currentName = this.current.name;
    }
    
    navigate(target) {
        this.name = target;
        console.log(this.currentName);
    }
}

and this is App.js:

<script>
    import { Router } from './router';
    const pageMap = {
        start: {
            title: 'start',
            name: 'world!',
        },
        end: {
            title: 'end',
            name: '-- it works!!!',
        },
    }
    const page = new Router(pageMap);
    page.name = 'start';
</script>

<h1>Hello {page.currentName}</h1>
<button on:click={() => page.navigate('end')}>
    change to "it works"
</button>
<button on:click={() => page.navigate('start')}>
    change back to "world!"
</button>

The desired behavior is that the page.currentName value changes with the button presses. The output to the console on button presses is correct: "--it works!!!", or "world!". However, the text remains "Hello world!", so the value change is not traveling outside the class instance. If I had some way of saying "this = this" upon invoking the navigate method, that would probably solve the problem...

I suspect the correct answer involves writable stores, but I haven't quite been able to figure it out.

CodePudding user response:

I suspect the correct answer involves writable stores

That is correct and trying to use classes like this is not helpful, at least with how Svelte operates right now.

Stores have to be declared at the top level of a component to be usable with $ syntax, putting them inside properties of classes and hiding them behind getters or setters just gets in the way.

I would just use a function that returns an object containing the stores and API you actually need, which then can be destructured right away and used in the markup. E.g.

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

export function router(pageMap) {
    const current = writable({});
    const currentName = derived(current, $current => $current.name ?? '');

    function navigate(target) {
        current.set(pageMap[target]);
    }
    
    return {
        navigate,
        currentName,
    };
}
<script>
    import { router } from './router';
    const pageMap = {
        start: {
            title: 'start',
            name: 'world!',
        },
        end: {
            title: 'end',
            name: '-- it works!!!',
        },
    }
    const { navigate, currentName } = router(pageMap);
    navigate('start');
</script>

<h1>Hello {$currentName}</h1>
<button on:click={() => navigate('end')}>
    change to "it works"
</button>
<button on:click={() => navigate('start')}>
    change back to "world!"
</button>

REPL example

You can do something similar with a class, but if you destructure it, the this binding will be broken, so all functions have to be bound manually or you have to pull out the store on its own and keep accessing the functions via the instance.

REPL example

  • Related