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:
Even though Angular is used for prototype applications, it should work well in general for:
If you like the @fireflysemantics/slice API please star our Github Repository.
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 Slice with the nanoid
peer dependency:
v17.0.x
for Angular 17v16.2.x
for Angular 16v15.2.x
for Angular 15So for example for an Angular 15 project run.
npm i @fireflysemantics/slice@15.2.x nanoid
For Angular 17 run.
npm i @fireflysemantics/slice@lastest nanoid
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.
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)}`);
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: 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}`);
});
.spec
filePredicate<E>
filtering that is Observable
Predicate
based snapshots of entitiescount
of entities in the entity store. The count
feature can also be Predicate
filtered.gid
) and server id (id
) id property names for entities. observe
.The Typedoc API Reference includes simple examples of how to apply the API for the various stores, methods, and classes included.
Firefly Semantics Slice on Stackoverflow
Run npm run c
to build the project. The build artifacts will be stored in the dist/
directory.
Run npm run test
to execute the unit tests.
See the test cases.
Generated using TypeDoc