Introduction to the Firefly Semantics Slice Reactive Entity Store | Guide

Ole Ersoy
Feb - 08  -  7 min

Introduction

This guide is also available on youtube here.

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

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

Each EStore<E> instance also has utility Observables used to indicate whether the store is loading or if a search associated with the store is being conducted as indicated by the searching property.

In this guide we will use EStore<E> instances 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 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.

Observing Store Entities

The store can be observed with the obs property bound to the store instance.

store.obs.subscribe(todos => {
   console.log(todos)
})

The above snippet will log the initial todo entities that the store is initialized with. Whenever we perform operations (post, put, delete ) on the store the obs will fire notifying the subscriber function.

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

Sorting Observed Entities

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

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

To retrieve a Todo[] array snapshot of all the entities in the store call allSnapshot():

const snapshot:Todo[] = 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 unique 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);

Indicate that the Store is Loading

store.loading = true
// When done loading
store.loading = false
// Observe Store Loading
const loading$ = store.observeLoading()
loading$.subscribe(
    loading => {
       console.log(loading);  
});

Indicate that the Store is Searching

store.searching = true;
// When done searching
store.searching = false
// Observe Store Searching
const searching$ = store.observeSearching()
searching$.subscribe(
    loading => {
       console.log(loading);  
});

Set the Store Query

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

store.query = "Slice is Awesome!"

Observe the Store Query

const query$ = store.observeQuery()
Indicate That the Store is Being Searched
store.searching = true
// When done loading
store.searching = false

Add / Remove Active Entities

store.addActive(todo1)
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) 
});

Reset the Store

store.reset()

Check Store Entity Count

store.countSnapshot()

Observe Store Entity Count

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

Contains Entity

To check whether the store contains entity call contains:

let containsEntity:boolean = store.contains(entity)

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$.