DEV Community

Cover image for Supercharge Your Svelte State Management with Akita
Inbal Sinai
Inbal Sinai

Posted on

5 1

Supercharge Your Svelte State Management with Akita

There’s a new player in the world of JS frameworks — Svelte. As its name suggests, Svelte is lean, extremely lean. In fact, Svelte’s modus operandi, of compiling code to (ideal) vanilla js during the build phase rather than interpreting it during the run phase, has had people describing it as a non-framework and its own creator calling it a language, rather than a framework. Specifically, one created for describing reactive user interfaces.

I’ve started playing around with Svelte and so far I’ve been having a blast. The tutorial offered by creator Rich Harris functions as a very pleasant introduction. After completing it, I started to wonder how Akita, the state management solution we’ve created here in Datorama, would work in conjunction with Svelte.

Svelte + Akita = A Great Combo

If you’re already familiar with Svelte, you might be asking yourself why additional state management is even required, as Svelte comes with built-in reactive store functionality. If you have a small application, you can probably get away with using Svelte API alone. However, larger and more complex applications require a full-fledged state management solution. It’s similar to how we need Redux when we have React’s Context API.

A note on Akita’s architecture:

Akita’s architecture

The two main components of Akita are the store and the query. You can think of the store like a table that contains data in a database, and you can perform various actions on it like inserting, updating, etc. My svelte component will reactively get the data from the query. And since I want it to remain agnostic of its data source, I won’t import the store inside the component; Instead, I’ll create a service for the component to work with.

As you can see, Akita defines a strict pattern for managing your state data. It comes with powerful Entity management, a robust set of plugins and dev-tools, and its own cli. Svelte and Akita are a great natural combo because Akita’s queries can return observables, which Svelte supports out of the box. So here is one example of how the two can be used in tandem:

Incorporating Akita

To add Akita to my svelte app, I install it, and its cli, via npm. I start with a fresh Svelte seed and go ahead with creating the traditional “TODO” app using Svelte and Akita.

The TODO Entity Store

Since we’re dealing with TODO items, I opt to create an Entity Store in Akita. You can think of an entity store as a table in a database where each row represents an entity. To do that I use the Akita CLI.

The store itself has all the built-in methods I need in order to perform CRUD operations on my entities.

import { createEntityStore } from '@datorama/akita';
const initialState = {
filter: "SHOW_ALL"
};
export const todosStore = createEntityStore(initialState, {
name: 'todos'
});
view raw 009-01.js hosted with ❤ by GitHub

In addition to those methods, I add a filter parameter in the store creation, representing the UI State data used to filter the display of the TODO items.

The TODO Service

Next, I’ll create the aforementioned service:

import { todosStore } from './todos.store';
import { guid } from '@datorama/akita';
export async function readTodos() {
//simulation of server call for reading the TODO items
const todos = await Promise.resolve([{id:1, completed: true, title: 'create sample Svelte+Akita todo app'}]);
todosStore.set(todos);
}
export async function addTodo(title) {
const todo = { title, completed: false};
//simulation of server call for saving the TODO item, which returns the generated id
const idFromServer = await Promise.resolve(guid());
todosStore.add({ id: idFromServer, ...todo });
}
export async function toggleCompleted(id, completed) {
//simulation of server call for updating the TODO item
await Promise.resolve();
todosStore.update(id, { completed });
}
export async function removeTodo(id) {
//simulation of server call for deleting the TODO item
await Promise.resolve();
todosStore.remove(id);
}
export function updateFilter(filter) {
todosStore.update({ filter });
}
view raw 009-02.js hosted with ❤ by GitHub

It contains all the methods I’ll need to update/read from the server and the store.

The TODO Entity Query

Akita Entity Query’s built-in methods can be divided into two types. A method that starts with select, for which you get an observable for the requested data, and methods that start with get, in which you get the requested data directly. It’s important to emphasize that the subscription won’t be triggered unless the value you asked is changed by reference. To those, I add the following methods:

import { createEntityQuery } from '@datorama/akita';
import { todosStore } from './todos.store';
import { combineLatest } from "rxjs";
export const todosQuery = createEntityQuery(todosStore);
export const selectFilter = todosQuery.select('filter');
export const visibleTodos = combineLatest(
selectFilter,
todosQuery.selectAll(),
function getVisibleTodos(filter, todos) {
switch (filter) {
case "SHOW_COMPLETED":
return todos.filter(t => t.completed);
case "SHOW_ACTIVE":
return todos.filter(t => !t.completed);
default:
return todos;
}
}
);
view raw 009-03.js hosted with ❤ by GitHub

I use combineLatest to subscribe to the store, get the latest version of the TODO list, and filter it according to the latest filter selection.

The next step is to create the Svelte components that interact with the Akita Entity store:

The Filter Component

This component’s sole responsibility is to let the users determine the criteria by which they filter the TODO list:

<svelte:options immutable />
<script>
export let currentFilter;
const filters = [
{
id: "SHOW_ALL",
label: "All"
},
{
id: "SHOW_ACTIVE",
label: "Active"
},
{
id: "SHOW_COMPLETED",
label: "Completed"
}
];
</script>
<div class="btn-group">
{#each filters as filter}
<button type="button" class="btn btn-outline-dark"
class:active={filter.id === $currentFilter ? 'active' : ''}
data-filter-id="{filter.id}" on:click>{filter.label}</button>
{/each}
</div>
view raw 009-04.svelte hosted with ❤ by GitHub

The Add TODO Component

This component is in charge of dispatching a todo event for the purpose of adding a TODO:

<svelte:options immutable />
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
let todo = "";
</script>
<div class="form-group">
<input
class="form-control"
placeholder="Add todo.."
on:keydown={event => {
if (event.key === 'Enter') {
dispatch('todo', todo);
todo = '';
}
}}
bind:value={todo} />
</div>
view raw 009-05.svelte hosted with ❤ by GitHub

The TODO Component

It is used to display a single TODO item, along with a button for deleting it, and a checkbox to toggle its ‘completed’ status:

<svelte:options immutable />
<script>
export let todo;
</script>
<li class="list-group-item">
<div>
<input type="checkbox" checked={todo.completed} on:change />
{todo.title}
</div>
<button type="button" class="btn btn-sm btn-danger" on:click>X</button>
</li>
view raw 009-06.svelte hosted with ❤ by GitHub

Connecting the Dots

Finally, a TODOs component will use all the previously mentioned components, along with the query and service I’ve created, to manage my list:

<svelte:options immutable />
<script>
import {
visibleTodos,
addTodo,
readTodos,
removeTodo,
selectFilter,
updateFilter,
toggleCompleted
} from "./state";
import Todo from "./Todo.svelte";
import AddTodo from "./AddTodo.svelte";
import Filters from "./Filters.svelte";
let todo = "";
readTodos();
</script>
<section>
<h1>Todos</h1>
<Filters currentFilter={selectFilter}
on:click={event => updateFilter(event.target.dataset.filterId)}/>
<AddTodo on:todo={ e => addTodo(e.detail) }/>
<ul class="list-group">
{#each $visibileTodos as todo, i (todo.id)}
<Todo {todo}
on:click={() => removeTodo(todo.id)}
on:change={e => toggleCompleted(todo.id, e.target.checked)} />
{/each}
</ul>
{$visibileTodos.length}
</section>
view raw 009-07.svelte hosted with ❤ by GitHub

The End Result

Before we conclude, I wanted to add one last tidbit: Another thing you can get from Akita out of the box, is the ability to persist the state via local storage. I simply add in the main JS file:

import { akitaDevtools, persistState } from '@datorama/akita';
akitaDevtools();
persistState();
const app = new App({
target: document.body
});
view raw 009-08.js hosted with ❤ by GitHub

And voila, your state is now persistent!

You can find all the files for this example here.

In summary: Both Akita and Svelte offer a moderate learning curve and enable the easy creation of incredibly lean apps with super-efficient state management out of the box. I encourage you to try both, either together or separately 👯‍♀️

Follow me on Medium to read more about Svelte, Akita, JS!

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (1)

Collapse
 
jefferyhus profile image
El Housseine Jaafari

good article :)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay