Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[NG India] Event-Based State Management with Ng...

[NG India] Event-Based State Management with NgRx SignalStore

Event-driven architecture adds a layer of indirection that enhances decoupling, improves data flow predictability, and supports the scaling of complex systems.

While SignalStore does not natively support an event-based model, its flexible and highly extensible design opens the door to integrating various functionalities and applying diverse state management patterns.

In this talk, we will explore how event-driven principles can be used with SignalStore to enhance and scale the state management layer in complex Angular applications.

Marko Stanimirović

April 14, 2025
Tweet

More Decks by Marko Stanimirović

Other Decks in Programming

Transcript

  1. Event-Driven Architecture Producer 1 Producer 2 Consumer 1 Event Bus

    Consumer 2 Consumer 3 Event A Event B publishes
  2. Event-Driven Architecture Producer 1 Producer 2 Consumer 1 Event Bus

    Consumer 2 Consumer 3 Event A Event B Event A Event B Event B Event A publishes notifies
  3. Producer 1 Producer 2 Consumer 1 Event Bus Consumer 2

    Consumer 3 Event A Event B Event A Event B Event B Event A publishes notifies Event-Driven Architecture - Loose Coupling: Components don't need to know about each other. - Scalability: Individual components can be easily scaled. - Flexibility: As long as the event format is maintained, components can be changed or replaced independently. - Extensibility: New components can be introduced without changing existing code.
  4. Marko Stanimirović @MarkoStDev ★ Principal Frontend Engineer at SMG ★

    Core Team at NgRx and AnalogJS ★ Google Developer Expert in Angular ★ Angular Belgrade Organizer ★ Hobby Musician 🎸 ★ M.Sc. in Software Engineering
  5. Flux: Main Features Action Dispatcher Store View Action - Unidirectional

    Data Flow - Separation of Concerns - Scalable Architecture - Improved Debugging
  6. Gartner Hype Cycle: Flux Evolution Time Expectations 2014: Flux Introduced

    2016: Redux Peak Popularity 2019: Rise of Alternatives
  7. Gartner Hype Cycle: Flux Evolution Time Expectations 2014: Flux Introduced

    2016: Redux Peak Popularity Stable Mature Usage 2019: Rise of Alternatives
  8. Gartner Hype Cycle: Flux Evolution Time Expectations 2014: Flux Introduced

    2016: Redux Peak Popularity Stable Mature Usage 2019: Rise of Alternatives
  9. Lightweight Solutions - Reduced Complexity - Less “Boilerplate” Code -

    Gradual Learning Curve Zustand ComponentStore Pinia
  10. type BooksState = { books: Book[] }; export const BooksStore

    = signalStore( withState<BooksState>({ books: [] }), );
  11. type BooksState = { books: Book[] }; export const BooksStore

    = signalStore( withState<BooksState>({ books: [] }), withComputed(({ books }) *> ({ totalBooks: computed(() *> books().length), freeBooks: computed(() *> books().filter(({ price }) *> price **= 0)), })), );
  12. type BooksState = { books: Book[] }; export const BooksStore

    = signalStore( withState<BooksState>({ books: [] }), withComputed(({ books }) *> ({ totalBooks: computed(() *> books().length), freeBooks: computed(() *> books().filter(({ price }) *> price **= 0)), })), withMethods((store) *> ({ addBook(book: Book): void { patchState(store, { books: [**.store.books(), book] }); }, })), );
  13. @Component({ template: ` <h2>Add Book*/h2> <book-form (addBook)="store.addBook($event)" *> <h2>All Books*/h2>

    <book-list [books]="store.books()" *> <h2>Free Books*/h2> <book-list [books]="store.freeBooks()" *> `, providers: [BooksStore], }) export class BooksComponent { readonly store = inject(BooksStore); }
  14. @Component({ template: ` <h2>Add Book*/h2> <book-form (addBook)="store.addBook($event)" *> <h2>All Books*/h2>

    <book-list [books]="store.books()" *> <h2>Free Books*/h2> <book-list [books]="store.freeBooks()" *> `, providers: [BooksStore], }) export class BooksComponent { readonly store = inject(BooksStore); }
  15. @Component({ template: ` <h2>Add Book*/h2> <book-form (addBook)="store.addBook($event)" *> <h2>All Books*/h2>

    <book-list [books]="store.books()" *> <h2>Free Books*/h2> <book-list [books]="store.freeBooks()" *> `, providers: [BooksStore], }) export class BooksComponent { readonly store = inject(BooksStore); }
  16. ProductDetailsPage ★ ____ ★ ____ ★ ____ CartStore InventoryStore add

    product to cart reduce available quantity Add to Cart
  17. ProductDetailsPage ★ ____ ★ ____ ★ ____ CartStore InventoryStore RecommendationsStore

    add product to cart reduce available quantity load recommended products Add to Cart
  18. export const productDetailsPageEvents = eventGroup({ source: 'Product Details Page', events:

    { addedToCart: props<{ product: Product }>(), }, }); @Component({ ** **. */ }) export class ProductDetailsComponent { readonly dispatch = injectDispatch(productDetailsPageEvents); addToCart(product: Product) { this.dispatch.addedToCart({ product }); } }
  19. export const productDetailsPageEvents = eventGroup({ source: 'Product Details Page', events:

    { addedToCart: props<{ product: Product }>(), }, }); @Component({ ** **. */ }) export class ProductDetailsComponent { readonly dispatch = injectDispatch(productDetailsPageEvents); addToCart(product: Product) { this.dispatch.addedToCart({ product }); } }
  20. export const CartStore = signalStore( withReducer( when( productDetailsPageEvents.addedToCart, ({ product

    }) *> ({ ** add to cart */ }), ), ), ); export const InventoryStore = signalStore( withReducer( when( productDetailsPageEvents.addedToCart, ({ product }) *> ({ ** reduce quantity */ }), ), ), );
  21. export const CartStore = signalStore( withReducer( when( productDetailsPageEvents.addedToCart, ({ product

    }) *> ({ ** add to cart */ }), ), ), ); export const InventoryStore = signalStore( withReducer( when( productDetailsPageEvents.addedToCart, ({ product }) *> ({ ** reduce quantity */ }), ), ), ); export const RecommendationsStore = signalStore( withReducer( when( productDetailsPageEvents.addedToCart, setPending ) ), withEffects((_, events = inject(Events)) *> ({ loadRecommendations$: events .on(productDetailsPageEvents.addedToCart) .pipe(** load recommended products */), })), );
  22. - Combines proven patterns from Flux, NgRx Store, and RxJS.

    - Seamlessly integrates with existing SignalStore features. - Extends Flux architecture with powerful customization options. - Unifies local and global state management with a single approach. @ngrx/signals/events
  23. X: @ngrx_io LinkedIn: NgRx Discord: discord.gg/ngrx Docs: ngrx.io Blog: dev.to/ngrx

    GitHub: github.com/ngrx/platform Workshops: ti.to/ngrx