Introduction to the Firefly Semantics Slice Reactive Entity Store | Guide

Ole Ersoy
Feb - 08  -  7 min

Introduction

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 property can be assigned by the server (It is not managed by Slice). 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

We assigned a unqiue id to the entities, thus we can use it to check 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$.