Introduction to the Firefly Semantics Slice Reactive Entity Store

Ole Ersoy
5 min readFeb 12, 2021
Image by jplenio from Pixabay

Updated Version

There is an updated version of this guide here:

The Firefly Semantics Slice Entity Store ( EStore<E> ) contains an observable array of entity instances.

Use it in single page, web, mobile, and server side applications for reactive state management.

In this guide we will use it to store Todo entities and show applications of the API that EStore<E> supports.

Will will also add reactive Slice instances that track which Todo entities are complete and which are incomplete.

Stackblitz

Open a new Stackblitz Typescript project and add @fireflysemantics/slice to the dependencies. Notice the peer dependencies that are automatically pulled in.

This is a completed version. Feel free to fork it or add the snippets as we are going along.

Model

We will be storing Todo entities. This is the model:

interface Todo {
id?: string;
gid?: string;
complete: boolean;
title: string;
}

We are using Typescript interfaces.

Feel free to switch the interface out with classes, types, etc.

Entities

We will use these Todo entity instances for demoing:

const TODO_ID_1 = "1";
const TODO_ID_2 = "2";
const TODO1: Todo = {
id: TODO_ID_1,
complete: false,
title: "You complete me!"
};
const TODO2: Todo = {
id: TODO_ID_2,
complete: true,
title: "You completed me!"
};
let todos: Todo[] = [TODO1, TODO2];

ID Assignment

Our Todo interface has two ids. The gid (Global ID) is assigned by Slice, such that the store can always identify an entity once it is added.

The id can be assigned by the server. In this case we will initialize it while creating the sample data.

Generally speaking this type of approach supports Optimistic User Interfaces, where entities have an ID that allows both the client and the server to identify the entity consistently. For example consider a chat message. Before it is sent, it will have a unique ID generated for on the client. Once it hits the server the server can also assign a unique ID to it and now it will be consistent on both ends.

Constructor Initialization

let store: EStore<Todo> = new EStore<Todo>(todos);

Each entity will have a global ID ( gid ) assigned to it, unless the property is already initialized.

Retrieve Entities from the Store by ID

const todo1 = store.findOneByID(TODO_ID_1);
const todo2 = store.findOneByID(TODO_ID_2);

Retrieve Store Entities by Predicate

Select all Todo instances that have a title length greater than 10:

let predicateTodos:Todo[]=store.select(todo=>todo.title.length>10);

Retrieve a Snapshot of All Entities

const snapshot = store.allSnapshot();

Check Entity Equality by Global ID

Since the EStore assigns the gid for us, we can use it to compare entities for equality:

let todo1EqualsTodo1:boolean = store.equalsByGUID(todo1, todo1);

Check Entity Equality by ID

Since the EStore assigns the gid for us, we can use it to compare entities for equality:

let todo1EqualsTodo1 = store.equalsByID(todo1, todo1);

Add a Single Entity

Use post to post a single entity.

let store2: EStore<Todo> = new EStore<Todo>();
store2.post(todoPost);

Add an Array of Entities to the Store

Use postA to post an array of entities.

store2.postA(todos);

Add Multiple Individual Entities to the Store

Use postN to post multiple individual entities.

store2.postN(TODO1, TODO2);

Update a Single Store Entity

let store5 = new EStore<Todo>();
store5.post(TODO1);
let T1 = store5.findOneByID(TODO1.id);
T1.title = "This Todo has been Updated";
store5.put(T1);

Update an Array of Store Entities

let store6 = new EStore<Todo>(todos);
T1 = store6.findOneByID(TODO1.id);
T1.title = "This Todo has been Updated";
let T2 = store6.findOneByID(TODO2.id);
T2.title = "This Todo has also been Updated";
store5.putA([T1, T2]);

Update Multiple Individual Entities

let store7 = new EStore<Todo>(todos);
T1 = store7.findOneByID(TODO1.id);
T1.title = "This Todo has been Updated";
T2 = store7.findOneByID(TODO2.id);
T2.title = "This Todo has also been Updated";
store5.putN(T1, T2);

Delete a Single Entity

let store8 = new EStore<Todo>();
store8.post(TODO1);
T1 = store5.findOneByID(TODO1.id);
store5.delete(T1);

Delete an Array of Entities

let store9 = new EStore<Todo>(todos);
T1 = store9.findOneByID(TODO1.id);
T2 = store6.findOneByID(TODO2.id);
store5.deleteA([T1, T2]);

Delete Multiple Individual Entities

let store10 = new EStore<Todo>(todos);
T1 = store10.findOneByID(TODO1.id);
T2 = store10.findOneByID(TODO2.id);
store10.deleteN(T1, T2);

Observe the Entities in the Store

Whenever we add, update, or delete entities from the store the subscription will fire notifying the subscription function of the update to the array of Todo entities.

Below we simply log the new array contents:

let store11: EStore<Todo> = new EStore<Todo>(todos);store11.observable.subscribe(todos => {console.log("THE REACTIVE TODO ARRAY IS: \n");
console.log(JSON.stringify(todos));
});
T1 = store11.findOneByID(TODO1.id);
T2 = store11.findOneByID(TODO2.id);
store11.post({ id: "3", title: "Adding another Todo Entity", complete: false });
store11.delete(T1);
T2.title = "This Todo has also been Updated";
store11.put(T2);

We can also perform a sort the observed entities. In the below case the sort function sorts by title:

let todos$ = store11.observe((a, b)=>(a.title > b.title ? -1 : 1));

Indicate that the Store is Loading

store.loading = true
// When done loading
store.loading = false

Observe Store Loading Indicator

const loading$ = store.observeLoading()
loading$.subscribe(
loading => {
console.log(loading);
});

Set the Store Query

store.query = "Slice is Awesome!"

Setting the store.query property will trigger notification to any observers of this property.

Observe the Store Query

const query$ = store.observeQuery()

Indicate That the Store is Being Searched

store.searching = true
// When done loading
store.searching = false

Observe Store Searching Indicator

const searching$ = store.observeSearching()
searching$.subscribe(
searching => {
console.log(searching);
});

Indicate That an Entity is Active

store.addActive(todo1)

Remove an Active Entity

store.deleteActive(todo1)

Add an Entity Via Toggle

If todo1 is not in the store the toggle call will add it.

store.toggle(todo1)

Remove an Entity Via Toggle

If todo1 is in the store the toggle call will remove it.

store.toggle(todo1)

Check Whether the Store is Empty

store.isEmptySnapshot()

Observe Whether the Store is Empty

store.isEmpty().subscribe(empty => {   
console.log(empty)
});

Clear the Store

store.clear()

Check Store Entity Count

store.countSnapshot()

Observe Store Entity Count

store.count().subscribe( c => {    
console.log(c)
});

Adding Complete and Incomplete Slices

Slices are live observable filters. We will add two slices for complete and incomplete todos.

export const enum TodoSlices {COMPLETE = "Complete",INCOMPLETE = "Incomplete"}let store12 = new EStore<Todo>(todos);T1 = store12.findOneByID(TODO1.id);T2 = store12.findOneByID(TODO2.id);store12.addSlice(todo => todo.complete, TodoSlices.COMPLETE);store12.addSlice(todo => !todo.complete, TodoSlices.INCOMPLETE);const completeTodos = store12.getSlice(TodoSlices.COMPLETE).allSnapshot();const incompletedTodos = store12.getSlice(TodoSlices.INCOMPLETE).allSnapshot();const completeTodos$ = store12.getSlice(TodoSlices.COMPLETE).observe();completeTodos$.subscribe(todos => {console.log("COMPLETED TODOS");console.log(JSON.stringify(todos));});store12.post({id: "3",title: "Adding a Complete Todo",complete: true});

When we post the Todo instance with id 3 the completeTodos$ observable fires, and the Todo is logged by the subscriber of the completeTodos$.

--

--