signalstory
Version:
Signal-based state management for Angular that grows with your project. Explore a versatile toolbox with enriching plugins for developers at all levels.
210 lines (171 loc) ⢠7.63 kB
Markdown
# signalstory
### Check out the [sample š ](https://stackblitz.com/edit/stackblitz-starters-bjnmnr?file=src%2Fapp%2Fstate%2Fbooks.store.ts)
### Visit the [docs š ](https://zuriscript.github.io/signalstory/docs/prolog)
### For a full feature overview, visit the [Website ⨠](https://zuriscript.github.io/signalstory/)
### For Release notes and Changelog, visit [Gtihub Releases š§āāļø](https://github.com/zuriscript/signalstory/releases)
**Empower your angular state management with signals**
signalstory is a state management library based on angular signals. It offers a range of architectural options, from simple repository-based state management (`signal-in-a-service`) to orchestrating decoupled commands, handling side effects through encapsulated objects, and facilitating inter-store communication using an event-driven approach. The ultimate goal is to provide a great user experience for all developers, whether junior or senior, while incorporating all the features you need to master your frontend state requirements.
> Starting out? You can keep it nice and simple if you prefer to avoid exploring all the advanced features that a state management library can offer! Begin by checking out the [store](https://zuriscript.github.io/signalstory/docs/store), and only dive into the rest if you're curious later on.
Here's a snapshot of some notable highlights:
ā
Signal-in-a-service approach
ā
Simple, non-intrusive and lightweight
ā
Optimized for Scalability
ā
Imperative-first with Declaritive capabilities
ā
Immutability on demand
ā
Rich plugin ecosystem
ā
Native IndexedDB support
ā
Transactional Undo/Redo
ā
Global State Snaphots and Rollbacks
ā
Devtools support
ā
Effect and Store status tracking
ā
Realtime store performance statistics
ā
Custom plugin support
ā
Built-in testing utilities
ā
SSR friendly
ā
Tree-shakeable
## Guiding Principles
- š Use class methods to provide controlled access and mutations to shared state.
- š If your store becomes too complex and bloated, slice it into multiple stores.
- ⨠Join and aggregate your state at the component level using signal mechanics.
- š Need to sync states between stores synchronously? - Use events.
- š® Need to decouple actors and consumers as you do in `redux`? - Use events.
- š Craving `Immutability`? - Just activate it.
- šļø Don't want full immutability because your store has to be super fast? - Don't activate it.
- š§āāļø Seeking a way to encapsulate side effects in a reusable, maintainable, and testable way? - Use effect objects.
- š Want a way to reuse and test queries spanning over multiple stores? - Use query objects.
- š¦ Don't want to use a class for stores? - You don't have to.
- š ļø Tired of debugging state changes in the console? - Enable redux devtools.
- šŖ Still want some good old logging magic? - Enable Store logger plugin
- ā³ Need to keep track of store history and perform undo/redo operations? - track the history.
- š¾ Want to sync your state with local storage? - Enable the persistence plugin.
- šļø Need a more sophisticated store storage or building an offline app? - Use IndexedDB adapter
- š Need to get notified of whether your store is modified or currently loading? - Enable the Store Status plugin.
- š Wondering where your bottlenecks are? - Enable the performance counter plugin
- šØ Something's missing? - Write a custom plugin.
- š Read the [docs](https://zuriscript.github.io/signalstory/) for more features and concepts.
## Installation
Install the library using npm:
```shell
npm install signalstory
```
## Sneak peek
```typescript
import { produce } from 'immer';
// Immutable store class using immer.js for boosting immutable mutations
@Injectable({ providedIn: 'root' })
class BookStore extends ImmutableStore<Book[]> {
constructor() {
super({
initialState: { ... },
name: 'Books Store',
mutationProducerFn: produce,
plugins: [
useDevtools(),
usePerformanceCounter(),
useLogger(),
useStoreStatus(),
useStorePersistence(
configureIndexedDb({
dbName: 'SharedDatabase',
})),
],
});
// Handle store reset request events
this.registerHandler(storeResetRequestEvent, store => {
store.set([], 'Reset');
});
}
// Query
public get getBooksInCollection() {
return computed(() => this.state().filter(x => x isInCollection));
}
// Command
public addToCollection(bookId: string) {
this.mutate(state => {
const book = state.find(x => x.id === bookId);
if (book) {
book.isInCollection = true;
}
}, 'Add Book To Collection');
}
}
```
```typescript
// Encapsulated multi store query object
export const BooksAndPublishersByAuthorInSwitzerlandQuery = createQuery(
[BookStore, PublisherStore],
(books, publishers, authorId: string) => {
const booksFromAuthor = books.state().filter(x => x.author === authorId);
const publishersInSwitzerland = publishers
.state()
.filter(x => x.country === 'CH');
return booksFromAuthor.map(book => ({
book,
publisher: publishersInSwitzerland.find(
p => p.id === book.mainPublisherId
),
}));
}
);
// And then run it
const query = myBookStore.runQuery(
BooksAndPublishersByAuthorInSwitzerlandQuery,
'sapowski'
);
```
```typescript
// Encapsulated effect object
export const fetchBooksEffect = createEffect(
'Fetch Books',
(store: BookStore) => {
const service = inject(BooksService);
const notification = inject(NotificationService);
return service.fetchBooks().pipe(
catchError(err => {
notification.alertError(err);
return of([]);
}),
tap(result => store.setBooks(result))
);
},
{
setLoadingStatus: true, // indicates that the store is loading while the effect runs
setInitializedStatus: true, // it should mark the store as initialized upon completion
}
);
// And then run it
myBookStore.runEffect(fetchBooksEffect).subscribe();
const loadingSignal = isLoading(myBookStore); // true while effect is running
const initializedSignal = initialized(myBookStore); // true after initializing effect completion
const modifiedSignal = modified(myBookStore); // true after store update
```
```typescript
// Track history spanning multiple stores
const tracker = trackHistory(50, store1, store2);
// Undo single commands
store1.set({ value: 10 }, 'ChangeCommand');
tracker.undo();
tracker.beginTransaction('Transaction Label');
store1.set({ value: 42 }, 'ChangeCommand');
store2.set({ value: 23 }, 'AnotherCommand');
tracker.endTransaction();
// Undo both commands on store1 and store2 at once
tracker.undo();
// Redo the whole transaction
tracker.redo();
```
## Sample Application
To set up and run the sample app locally, follow the steps below:
1. **Clone the repository:** Clone the repository containing the signalstory library and the sample app.
2. **Install dependencies:** Navigate to the root directory of the repository and run the following command to install the necessary dependencies:
```bash
npm install
```
3. **Build the library:** Run the following command to build the signalstory library:
```bash
ng build signalstory
```
4. **Serve the sample app:** Run the following command to serve the sample app locally:
```bash
ng serve sample --open
```