Home >Web Front-end >JS Tutorial >The Signal Store from NGRX - breakdown of the main concepts
The Creator of NGRX Signal Store, Marko Stanimirovic describes here NgRx SignalStore: In-Depth Look at Signal-Based State Management in Angular
Let’s explore the store's API with code examples. We’ll use a project with a list of product and filtering features.
import { signalStore } from "@ngrx/signals"; export const ProductStore = signalStore( … );
As with any NGRX Store so far there is an initial state that can be provided, using the function withState which accepts object literals, records or factory functions (for creating a dynamic initial state) as inputs.
import { signalStore, withState } from "@ngrx/signals"; const initialProductState: ProductState = { products: [] }; export const ProductStore = signalStore( withState(initialProductState); );
import { signalStore, withComputed, withState } from "@ngrx/signals"; export const ProductStore = signalStore( withState(initialProductState), withComputed(({products}) => ({ averagePrice: computed(() => { const total = products().reduce((acc, p) => acc + p.price, 0); return total / products().length; }) })),
import { signalStore, withComputed, withState, withMethods } from "@ngrx/signals"; export const ProductStore = signalStore( withState(initialProductState), withComputed(({products}) => ({ averagePrice: computed(() => { const total = products().reduce((acc, p) => acc + p.price, 0); return total / products().length; }) })), // CRUD operations withMethods((store, productService = inject(ProductService), ) => ({ loadProducts: () => { const products = toSignal(productService.loadProducts()) patchState(store, { products: products() }) }, addProduct: (product: Product) => { patchState(store, { products: [...store.products(), product] }); }, // ... })),
withMethods & withComputed get in a factory function and return a dictionary of methods and computed signals that can be accessed by using the store. They also run in an injection context, which makes it possible to inject dependencies into them.
import { withHooks } from "@ngrx/signals"; export const ProductStore = signalStore( withHooks((store) => ({ onInit() { // Load products when the store is initialized store.loadProducts(); }, })), );
export const ProductStoreWithEntities = signalStore( withEntities<Product>(), // CRUD operations withMethods((store, productService = inject(ProductService), ) => ({ loadProducts: () => { const products = toSignal(productService.loadProducts())(); patchState(store, setAllEntities(products || [])); }, updateProduct: (product: Product) => { productService.updateProduct(product); patchState(store, setEntity(product)); }, })),
It’s possible to add multiple features which start with “with” but they can access only what was defined before them.
signalStoreFeature - used for extending the functionality of the store.
Stores can get complex and hard to manage for big enterprise applications. When writing features and components for a project, the better and more granular the split, the easier to manage, maintain the code and write tests for it.
However, considering the API that SignalStore provides the store can get hard to manage unless code is splitted accordingly. signalStoreFeature is suitable for extracting specific functionality of a feature (or component) into a standalone testable function which potentially (and ideally) can be reused in other stores.
export const ProductStore = signalStore( // previous defined state and methods // Externalizing filtering options withFilteringOptions(), ); export function withFilteringOptions() { return signalStoreFeature( // Filtering operations withMethods(() => ({ getProductsBetweenPriceRange: (lowPrice: number, highPrice: number, products: Array<Product>, ) => { return products.filter(p => p.price >= lowPrice && p.price <= highPrice); }, getProductsByCategory: (category: string, products: Array<Product>) => { return products.filter(p => p.category === category); }, })), ); }
Now an example of signalStoreFeature that shows the possibility to reuse signalStoreFeature(s) across multiple stores.
import { patchState, signalStoreFeature, withMethods } from "@ngrx/signals";
export function withCrudOperations() { return signalStoreFeature( withMethods((store) => ({ load: (crudService: CrudOperations) => crudService.load(), update: (crudableObject: CRUD, crudService: CrudOperations) => { crudService.update(crudableObject); patchState(store, setEntity(crudableObject)); }, }), )); } export interface CrudOperations { load(): void; update(crudableObject: CRUD): void; } // Product & Customer services must extend the same interface. export class ProductService implements CrudOperations { load(): void { console.log('load products'); } update(): void { console.log('update products'); } } export class CustomerService implements CrudOperations { load(): void { console.log('load customers'); } update(): void { console.log('update customers'); } } // and now let’s add this feature in our stores export const ProductStore = signalStore( withCrudOperations(), ); export const CustomerStore = signalStore( withCrudOperations(), );
Being that easy to extend, there is already a utility package called ngrx-toolkit meant to add useful tools to Signal Stores.
{ providedIn: ‘root’ } or in the providers array of a Component, Service, Directive, etc.
It remains to be proven how reliable it is for larger applications, especially when applied as a global store.
For now I think it's a great addition to the default Signal API, making it a good option for managing:
https://www.stefanos-lignos.dev/posts/ngrx-signals-store
https://www.angulararchitects.io/blog/the-new-ngrx-signal-store-for-angular-2-1-flavors/ (group of 4 articles on the topic)
https://ngrx.io/guide/signals
The above is the detailed content of The Signal Store from NGRX - breakdown of the main concepts. For more information, please follow other related articles on the PHP Chinese website!