@fireflysemantics/slice
v17.0.21
Published
![Slice](slicelogo.png)
Downloads
202
Maintainers
Readme
@fireflysemantics/slice
Table of Contents
- Overview
- Why Slice
- Install
- API Reference
- Object Store Core Use Cases
- Entity Store Core Use Cases
- Features
- Help Center Documentation and Media
- Getting Help
Overview
Lightweight Javascript Reactive State Management for Angular Applications.
The API is designed to be as minimal as possible and should deliver the same features as other comparable frameworks with about 1/3 the lines of code.
It offers two types of reactive data stores:
- Entity stores (EStore) for structured entity like data (Customer, Product, User, ...). Entity stores can be "Live filtered" by adding slices. For example separating Todo entities into complete and incomplete compartments. Slices are also obserable.
- Object store (Key value store) for unstructured data
Even though Angular is used for prototype applications, it should work well in general for:
- Single page applications
- Progressive web applications
- Node applications / Pure Javascript Applications
- Mobile Applications
If you like the @fireflysemantics/slice API please star our Github Repository.
Why Slice
We built Slice to make sharing state between Angular components, services, and other directives simple and in the process we targeted common use cases that should be handled by a state manager, such as updating a shopping cart count, emitting a search query, tracking active state, etc.
For performing state CRUD operations Slice uses a REST like API, which should be a familiar paradigm for most developers.
For example a Todo
entity store tracking todo entities can create, read, update, and delete Todo
entities as follows (This is just a tiny example of all the capabilities Slice has).
let store: EStore<Todo> = new EStore<Todo>();
//============================================
// Post (Create) a Todo instance in the store
//============================================
store.post(todo);
//============================================
// Snapshot of all the Todo entities in the
// store
//============================================
let snapshot: Todo[] = store.allSnapshot();
//============================================
// Observe the array of Todo instances in the
// store
//============================================
store.obs.subscribe((todos: Todo[]) => {
console.log(`The store is initialized with these Todo entities ${todos}`);
});
//============================================
// Delete a Todo instance in the store
//============================================
todo.complete = false;
store.put(todo);
//============================================
// Delete a Todo instance in the store
//============================================
store.delete(todo);
Install
Install Slice with the nanoid
peer dependency:
v17.0.x
for Angular 17v16.2.x
for Angular 16v15.2.x
for Angular 15
So for example for an Angular 15 project run.
npm i @fireflysemantics/[email protected] nanoid
For Angular 17 run.
npm i @fireflysemantics/slice@lastest nanoid
Usage
The project typedoc, in addition to providing more detailed API insight, syntax highlights all the examples provided here, thus you may want to check it out for a richer reading experience.
Object Store Core Use Cases
Here is a link to the Stackblitz Demo containing all of the below examples.
In this demo we are using simple string
values, but we could have used objects or essentially anything that can be referenced by Javascript.
import {
KeyObsValueReset,
ObsValueReset,
OStore,
OStoreStart,
} from '@fireflysemantics/slice';
const START: OStoreStart = {
K1: { value: 'V1', reset: 'ResetValue' },
};
interface ISTART extends KeyObsValueReset {
K1: ObsValueReset;
}
let OS: OStore<ISTART> = new OStore(START);
//============================================
// Log a snapshot the initial store value.
// This will log
// V1
//============================================
const v1Snapshot: string = OS.snapshot(OS.S.K1);
console.log(`The value for the K1 key is ${v1Snapshot}`);
//============================================
// Observe the initial store value.
// The subsription will log
// V1
//============================================
OS.S.K1.obs.subscribe((v) => console.log(`The subscribed to value is ${v}`));
//============================================
// Update the initial store value
// The subsription will log
// New Value
//============================================
OS.put(OS.S.K1, 'New Value');
//============================================
// Log a count of the number of entries in the
// object store.
// This will log
// 1
//============================================
const count: number = OS.count();
console.log(
`The count of the number of entries in the Object Store is ${count}`
);
//============================================
// Reset the store
// The subsription will log
// ResetValue
//
// However if we had not specified a reset
// value it would have logged in value
// V1
//============================================
OS.reset();
//============================================
// Delete the K1 entry
// The subsription will log and the snapshot
// will also be
// undefined
//============================================
OS.delete(OS.S.K1);
const snapshot: string = OS.snapshot(OS.S.K1);
console.log(`The deleted value snapshot for the K1 key is ${snapshot}`);
//============================================
// Clear the store. First we will put a new
// value back in the store to demonstrate it
// being cleared.
//============================================
//============================================
// Update the initial store value
// The subsription will log
// New Value
//============================================
OS.put(OS.S.K1, 'V2');
OS.clear();
//============================================
// Count the number of values in the store
// It will be zero.
// The OS.clear() call will remove all the
// entries and so the snapshot will be undefined
// and the subscribed to value also undefined.
// The count will be zero.
//============================================
console.log(`The count is ${OS.count()}`);
console.log(`The snapshot is ${OS.snapshot(OS.S.K1)}`);
Entity Store Core Use Cases
Here is a link to the Stackblitz demo containing the below demo code. You may also wish to check out the test cases for the entity store which also detail usage scenarios.
//============================================
// Demo Utilities
//============================================
export const enum TodoSliceEnum {
COMPLETE = 'Complete',
INCOMPLETE = 'Incomplete',
}
export class Todo {
constructor(
public complete: boolean,
public title: string,
public gid?: string,
public id?: string
) {}
}
export const extraTodo: Todo = new Todo(false, 'Do me later.');
export let todos = [
new Todo(false, 'You complete me!'),
new Todo(true, 'You completed me!'),
];
export function todosFactory(): Todo[] {
return [
new Todo(false, 'You complete me!'),
new Todo(true, 'You completed me!'),
];
}
export function todosClone(): Todo[] {
return todos.map((obj) => ({ ...obj }));
}
//============================================
// API: constructor()
//
// Create a Todo Entity Store
//============================================
let store: EStore<Todo> = new EStore<Todo>(todosFactory());
//============================================
// API: post, put, delete
//
// Perform post (Create), put (Update), and delete opeartions
// on the store.
//============================================
const todoLater: Todo = new Todo(false, 'Do me later.');
todoLater.id = 'findMe';
store.post(todoLater);
const postedTodo = store.findOneByID('findMe');
postedTodo.title = 'Do me sooner';
store.put(postedTodo);
store.delete(postedTodo);
//============================================
// API: allSnapshot()
//
// Take a snapshot of all the entities
// in the store
//============================================
let snapshot: Todo[] = store.allSnapshot();
//============================================
// API: obs
//
// Create a subscription to the entities in
// the store.
//============================================
let todosSubscription: Subscription = store.obs.subscribe((todos: Todo[]) => {
console.log(`The store todos ${todos}`);
});
//============================================
// API: findOne()
//
// Find a Todo instance using the
// Global ID (guid) property.
//============================================
const globalID: string = '1';
let findThisTodo = new Todo(false, 'Find this Todo', globalID);
store.post(findThisTodo);
const todo = store.findOne(globalID);
console.log(todo);
//============================================
// API: findOneByID()
//
// Find a Todo instance using the
// ID (id) property.
//============================================
const ID: string = 'id';
let todoWithID = new Todo(false, 'Find this Todo by ID');
todoWithID.id = ID;
store.post(todoWithID);
const todoFoundByID = store.findOneByID(ID);
console.log(`The Todo instance found by id is ${todoFoundByID}`);
//============================================
// API: select()
//
// Select Todo instances where the title
// includes the string Find.
//============================================
const selectLaterPredicate: Predicate<Todo> = (todo: Todo) => {
return todo.title.includes('Find');
};
const selections = store.select(selectLaterPredicate);
console.log(
`The selected todo instances that contain Find are: ${selections.length}`
);
//============================================
// API: observeLoading()
//
// Subscribe to the store loading indicator
// and toggle it to see the values change.
//============================================
store.observeLoading().subscribe((loading) => {
console.log(`Is data loading: ${loading}`);
});
store.loading = true;
store.loading = false;
//============================================
// API: observeSearching()
//
// Subscribe to the store searching indicator
// and toggle it to see the values change.
//============================================
store.observeSearching().subscribe((searching) => {
console.log(`Is the store searching: ${searching}`);
});
store.searching = true;
store.searching = false;
//============================================
// API: addActive()
// Perform active state tracking. Initially the
// number of active entities will be zero.
//============================================
console.log(`The number of active Todo instances is ${store.active.size}`);
let todo1: Todo = new Todo(false, 'The first Todo!', GUID());
let todo2: Todo = new Todo(false, 'The first Todo!', GUID());
store.addActive(todo1);
console.log(`The number of active Todo instances is ${store.active.size}`);
console.log(
`The number of active Todo instances by the activeSnapshot is ${
store.activeSnapshot().length
}`
);
//============================================
// API: observeActive()
//
// Subscribing to the observeActive() observable
// provides the map of active Todo instances.
//============================================
store.observeActive().subscribe((active) => {
console.log(`The active Todo instances are: ${active}`);
});
//============================================
// API: deleteActive()
// Delete the active Todo instance.
// This will set the number of active
// Todo instances back to zero.
//============================================
store.deleteActive(todo1);
console.log(
`The number of active Todo instances by the activeSnapshot is ${
store.activeSnapshot().length
}`
);
//============================================
// API: count() and snapshotCount()
//
// Take snapshot and observable
// the counts of store entities
//============================================
const completePredicate: Predicate<Todo> = function pred(t: Todo) {
return t.complete;
};
const incompletePredicate: Predicate<Todo> = function pred(t: Todo) {
return !t.complete;
};
store.count().subscribe((c) => {
console.log(`The observed count of Todo entities is ${c}`);
});
store.count(incompletePredicate).subscribe((c) => {
console.log(`The observed count of incomplete Todo enttiies is ${c}`);
});
store.count(completePredicate).subscribe((c) => {
console.log(`The observed count of complete Todo enttiies is ${c}`);
});
const snapshotCount = store.countSnapshot(completePredicate);
console.log(`The count is ${snapshotCount}`);
const completeSnapshotCount = store.countSnapshot(completePredicate);
console.log(
`The complete Todo Entity Snapshot count is ${completeSnapshotCount}`
);
const incompleteSnapshotCount = store.countSnapshot(incompletePredicate);
console.log(
`The incomplete Todo Entity Snapshot count is ${incompleteSnapshotCount}`
);
//============================================
// API: toggle()
//
// When we post another todo using toggle
// instance the subscribed to count
// dynamically increases by 1.
// When we call toggle again,
// removing the instance the
// count decreases by 1.
//============================================
store.toggle(extraTodo);
store.toggle(extraTodo);
//============================================
// API: contains()
//
// When we post another todo using toggle
// the store now contains it.
//============================================
console.log(
`Does the store contain the extraTodo ${store.contains(extraTodo)}`
);
store.toggle(extraTodo);
console.log(
`Does the store contain the extraTodo ${store.contains(extraTodo)}`
);
store.toggle(extraTodo);
console.log(
`Does the store contain the extraTodo ${store.contains(extraTodo)}`
);
//============================================
// API: containsbyID()
//
// When we post another todo using toggle
// the store now contains it.
//
// Note the containsByID() can be called with
// both the id property or the entire instance.
//============================================
let todoByID = new Todo(false, 'This is not in the store', undefined, '1');
store.post(todoByID);
console.log(
`Does the store contain the todoByID ${store.containsById(todoByID.id)}`
);
console.log(
`Does the store contain the todoByID ${store.containsById(todoByID)}`
);
store.toggle(todoByID);
console.log(
`Does the store contain the todoByID ${store.containsById(todoByID.id)}`
);
console.log(
`Does the store contain the todoByID ${store.containsById(todoByID)}`
);
//============================================
// API: equalsByGUID and equalsByID
//
// Compare entities by ID and Global ID (guid).
// We will assign the ID and the global ID
// instead of allowing the global ID to be
// assigned by the store on post.
//============================================
const guid = GUID();
let todoOrNotTodo1 = new Todo(false, 'Apples to Apples', guid, '1');
let todoOrNotTodo2 = new Todo(false, 'Apples to Apples', guid, '1');
const equalByID: boolean = store.equalsByID(todoOrNotTodo1, todoOrNotTodo2);
console.log(`Are the todos equal by id: ${equalByID}`);
const equalByGUID: boolean = store.equalsByGUID(todoOrNotTodo1, todoOrNotTodo2);
console.log(`Are the todos equal by global id: ${equalByGUID}`);
//============================================
// API: addSlice
//
// Add a slice for complete todo entities.
//
// We create a new store to demo with a
// consistent count.
//
// When posting the extraTodo which is
// incomplete, we see that the incomplete
// count increments.
//============================================
store.destroy();
store = new EStore<Todo>(todosFactory());
store.addSlice((todo) => todo.complete, TodoSliceEnum.COMPLETE);
store.addSlice((todo) => !todo.complete, TodoSliceEnum.INCOMPLETE);
const completeSlice = store.getSlice(TodoSliceEnum.COMPLETE);
const incompleteSlice = store.getSlice(TodoSliceEnum.INCOMPLETE);
completeSlice.count().subscribe((c) => {
console.log(`The number of entries in the complete slice is ${c}`);
});
incompleteSlice.count().subscribe((c) => {
console.log(`The number of entries in the incomplete slice is ${c}`);
});
store.post(extraTodo);
const incompleteTodos: Todo[] = incompleteSlice.allSnapshot();
console.log(`The incomplete Todo entities are ${incompleteTodos}`);
//============================================
// API: isEmpty()
//
// Check whether the store is empty.
//============================================
store.isEmpty().subscribe((empty) => {
console.log(`Is the store empty? ${empty}`);
});
Features
- Live Stackblitz demoes
- Typedoc with inlined examples
- Well documented test cases run with Jest - Each file has a corresponding
.spec
file - Stream both Entity and Object Stores for UI Updates via RxJS
- Define entities using Typescript classes, interfaces, or types
- Active state tracking
- Supports for Optimistic User Interfaces
- RESTful API for performing CRUD operations that stream both full and delta updates
- Dynamic creation of both object and entity stores
- Observable delta updates for Entities
- Real time application of Slice
Predicate<E>
filtering that isObservable
Predicate
based snapshots of entities- Observable
count
of entities in the entity store. Thecount
feature can also bePredicate
filtered. - Configurable global id (Client side id -
gid
) and server id (id
) id property names for entities. - The stream of entities can be sorted via an optional boolean expression passed to
observe
.
Firefly Semantics Slice Development Center Media and Documentation
Concepts
Guides
- An Introduction to the Firefly Semantics Slice Reactive Object Store
- Introduction to the Firefly Semantics Slice Reactive Entity Store
- Creating a Reactive Todo Application With the Firefly Semantics Slice State Manager
- Recreating the Ngrx Demo with Slice
- Firefly Semantics Slice Entity Store Active API Guide
Tasks
- Creating a Minimal Slice Object Store
- Creating a Minimal Angular Slice Object Store Angular State Service
- Changing the Firefly Semantics Slice EStore Default Configuration
- Observing the Currently Active Entities with Slice
- Derived Reactive Observable State with Slice
- Reactive Event Driven Actions with Firefly Semantics Slice
- Unsubscribing From Firefly Semantics Slice Object Store Observables in Angular
- Creating Proxies to Slice Object Store Observables
- Getting a Snapshot of a Slice Object Store Value
- Accessing Slice Object Store Observables In Angular Templates
- Observing the Count of Items in a Firefly Semantics Slice Entity Store
- Setting and Observing Firefly Semantics Slice Entity Store Queries
- Taking a Count Snapshot of a Firefly Semantics Slice Entity Store
- Taking a Query Snapshot of a Firefly Semantics Slice Entity Store
- Adding Slices to an Firefly Semantics Slice Entity Store
- Adding Search To the Firefly Semantics Slice Angular Todo Application
- Comparing Firefly Semantics Slice Entities
- Filtering Firefly Semantics Slice Object Store Observables by Property Value
Youtube
- What is Reactive State Management
- An Introduction to the Firefly Semantics Slice Reactive Object Store
- Introduction to the Firefly Semantics Slice Reactive Entity Store
- Creating a Reactive Todo Application With the Firefly Semantics Slice State Manager
- Recreating the Ngrx Demo with Slice
- Setting and Observing the Firefly Semantics Slice Entity Store Query
- Observing the Count of Items in a Firefly Semantics Slice Entity Store
- Taking a Count Snapshot of a Firefly Semantics Slice Entity Store
- Taking a Query Snapshot of a Firefly Semantics Slice Entity Store
- Adding Slices to an Firefly Semantics Slice Entity Store
- Adding Search To the Firefly Semantics Slice Angular Todo Application
- Converting the Angular Todo Application From Materialize to Angular Material
- Firefly Semantics Slice Entity Store Active API Guide
- Comparing Firefly Semantics Slice Entities
- Derived Reactive Observable State with Slice
Examples
API Reference
The Typedoc API Reference includes simple examples of how to apply the API for the various stores, methods, and classes included.
Getting Help
Firefly Semantics Slice on Stackoverflow
Build
Run npm run c
to build the project. The build artifacts will be stored in the dist/
directory.
Running unit tests
Run npm run test
to execute the unit tests.
Tests
See the test cases.