Maison > interface Web > js tutoriel > le corps du texte

The Signal Store from NGRX - breakdown of the main concepts

WBOY
Libérer: 2024-07-23 15:00:55
original
975 人浏览过

The Signal Store from NGRX - breakdown of the main concepts

Characteristics

  • signal based
  • functional and declarative
  • used for local or global state management
  • extensible with custom features

How does it compare to the NGRX Global Store?

  • more lightweight and simplified API
  • don't have to worry as much about the data flow
  • seems harder to misuse, like reusing actions
  • easier to extend

The Creator of NGRX Signal Store, Marko Stanimirovic describes here NgRx SignalStore: In-Depth Look at Signal-Based State Management in Angular

Class-based state management limitations:

  • Typing: It’s not possible to define dynamic class properties or methods that are strongly typed
  • Tree-shaking: Unused class methods won’t be removed from the final bundle
  • Extensibility: Multiple inheritance is not supported.
  • Modularity: Splitting selectors, updaters, and effects into different classes is possible, but not provided out of the box

Let’s explore the store's API with code examples. We’ll use a project with a list of product and filtering features.

Creating a SignalStore

  • signalStore function which returns an injectable service suitable to be injected and provided where needed to be used.
import { signalStore } from "@ngrx/signals";

export const ProductStore = signalStore( … );
Copier après la connexion

Providing State withState

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);
);
Copier après la connexion

Computed State withComputed

  • built on top of the computed function to create derived states (computed state) from the store
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;
   })
 })),
Copier après la connexion

Performing Operations withMethods

  • it’s the place where the store’s operations will be defined
  • these can be methods to update the store or perform some operations based on its current state
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] });
   },
   // ...
 })),
Copier après la connexion

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.

Hooking withHooks

  • lifecycle methods of the store, currently it has onInit and onDestroy methods
import { withHooks } from "@ngrx/signals"; 

export const ProductStore = signalStore(
 withHooks((store) => ({
   onInit() {
     // Load products when the store is initialized
     store.loadProducts();
   },
 })),
);

Copier après la connexion

Managing Collections withEntities

  • use it when having to manage data like “Products, Users, Clients, etc” where CRUD operations are necessary for that feature
  • it provides a set of APIs to manage collections, like: addEntity, setEntity, remoteEntity.
export const ProductStoreWithEntities = signalStore(
 withEntities(),


 // 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));
   },

 })),
Copier après la connexion

It’s possible to add multiple features which start with “with” but they can access only what was defined before them.

Creating custom features with signalStoreFeature

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, ) => {
     return products.filter(p => p.price >= lowPrice && p.price <= highPrice);
   },


   getProductsByCategory: (category: string, products: Array) => {
     return products.filter(p => p.category === category);
   },
 })),
 );
}
Copier après la connexion

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(),
);
Copier après la connexion

NGRX Toolkit utility package

Being that easy to extend, there is already a utility package called ngrx-toolkit meant to add useful tools to Signal Stores.

Injecting SignalStore

{ providedIn: ‘root’ } or in the providers array of a Component, Service, Directive, etc.

DeepSignals

  • nested state properties read as signals, generated lazily on demand

patchState

  • alternative API to set and update (of signal API) for updating store’s state, only needs to be provided the values we want to change

rxMethod

  • utility method which helps use RxJS together with the SignalStore or signalState

Lighter alternative with SignalState

  • SignalState provides an alternative to managing signal-based state in a concise and minimalistic manner.

Concluding thoughts

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:

  • component level state
  • feature based state

Additional Resources:

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 Signal Store from NGRX - breakdown of the main concepts的详细内容。更多信息请关注PHP中文网其他相关文章!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!