Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/zuriscript/signalstory

Signal-based state management for Angular applications
https://github.com/zuriscript/signalstory

Last synced: 23 days ago
JSON representation

Signal-based state management for Angular applications

Awesome Lists containing this project

README

        



signalstory



Signalstory - Angular state management with signals

Baked into classical angular services!



Documentation   📚   
Sample   🚀   
Website   🔥   
Release notes   ✨   

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![npm version](https://badge.fury.io/js/signalstory.svg)](https://badge.fury.io/js/signalstory)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
[![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)]()
[![coc-badge](https://img.shields.io/badge/codeof-conduct-ff69b4.svg?style=flat-square)]()
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)

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.

> [!TIP]
> 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

## Let the store grow with your project


## 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 {
constructor() {
super({
initialState: { ... },
name: 'Books Store',
mutationProducerFn: produce,
plugins: [
useDevtools(),
usePerformanceCounter(),
useLogger(),
useStoreStatus(),
useStorePersistence(
configureIndexedDb({
dbName: 'SharedDatabase',
})),
],
});

// Handle store reset request events. Note, the storeResetRequestEvent would
// be created or imported, see the events documentation for more details
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
```

---


signalstory


made with ❤️ by zuriscript