Home

Awesome

<h1 align="center"> <a target="_blank" href="https://zuriscript.github.io/signalstory/" > <img align="center" alt="signalstory" src="signalstory_banner.png" style="width:100%;" /> </a> </h1> <p align="center"> <b>Signalstory - Angular state management with signals</b><br> Baked into classical angular services! </p> <p align="center"> <a href="https://zuriscript.github.io/signalstory/docs/prolog" target="_blank" ><b>Documentation</b></a>&nbsp;&nbsp;&nbsp;📚&nbsp;&nbsp;&nbsp; <a href="https://stackblitz.com/edit/stackblitz-starters-bjnmnr?file=src%2Fapp%2Fstate%2Fbooks.store.ts" target="_blank" ><b>Sample</b></a>&nbsp;&nbsp;&nbsp;🚀&nbsp;&nbsp;&nbsp; <a href="https://zuriscript.github.io/signalstory/" target="_blank" ><b>Website</b></a>&nbsp;&nbsp;&nbsp;🔥&nbsp;&nbsp;&nbsp; <a href="https://github.com/zuriscript/signalstory/releases" target="_blank" ><b>Release notes</b></a>&nbsp;&nbsp;&nbsp;✨&nbsp;&nbsp;&nbsp; </p> <div align="center">

License: MIT npm version Commitizen friendly PRs coc-badge styled with prettier

</div>

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, 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

<picture> <source media="(prefers-color-scheme: dark)" srcset="docs/static/img/code_evolve_landscape_dark.png"> <img src="docs/static/img/code_evolve_landscape_light.png"> </picture>

Guiding Principles

Installation

Install the library using npm:

npm install signalstory

Sneak peek

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. 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');
  }
}
// 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'
);
// 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
// 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:

    npm install
    
  3. Build the library: Run the following command to build the signalstory library:

    ng build signalstory
    
  4. Serve the sample app: Run the following command to serve the sample app locally:

    ng serve sample --open
    

<p align="center"> <img align="center" alt="signalstory" src="signalstory.png" style="width:60px;" /> </p> <p align="center"> made with ❤️ by zuriscript </p>