{"id":7292,"date":"2023-07-05T13:30:50","date_gmt":"2023-07-05T11:30:50","guid":{"rendered":"https:\/\/www.angulararchitects.io\/?p=7292"},"modified":"2023-12-08T13:07:03","modified_gmt":"2023-12-08T12:07:03","slug":"the-new-ngrx-signal-store-for-angular-2-1-flavors","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/","title":{"rendered":"The new NGRX Signal Store for Angular: 3+n Flavors"},"content":{"rendered":"<div class=\"wp-post-series-box series-ngrx-signal-store-en wp-post-series-box--expandable\">\n\t\t\t<input id=\"collapsible-series-ngrx-signal-store-en6a145a8887056\" class=\"wp-post-series-box__toggle_checkbox\" type=\"checkbox\">\n\t\n\t<label\n\t\tclass=\"wp-post-series-box__label\"\n\t\t\t\t\tfor=\"collapsible-series-ngrx-signal-store-en6a145a8887056\"\n\t\t\ttabindex=\"0\"\n\t\t\t\t>\n\t\t<p class=\"wp-post-series-box__name wp-post-series-name\">\n\t\t\tThis is post 1 of 7 in the series <em>&ldquo;NGRX Signal Store&rdquo;<\/em>\t\t<\/p>\n\t\t\t<\/label>\n\n\t\t\t<div class=\"wp-post-series-box__posts\">\n\t\t\t<ol>\n\t\t\t\t\t\t\t\t\t<li><span class=\"wp-post-series-box__current\">The new NGRX Signal Store for Angular: 3+n Flavors<\/span><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/\">Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/ngrx-signal-store-deep-dive-flexible-and-type-safe-custom-extensions\/\">NGRX Signal Store Deep Dive: Flexible and Type-Safe Custom Extensions<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/the-ngrx-signal-store-and-your-architecture\/\">The NGRX Signal Store and Your Architecture<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/using-the-resource-api-with-the-ngrx-signal-store\/\">Using Angular&#8217;s Resource API with the NGRX Signal Store<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-event-api-for-the-ngrx-signal-store-a-quantum-of-redux\/\">The new Event API for the NgRx Signal Store: A Quantum of Redux<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/full-cycle-reativity-in-angular-signal-forms-signal-store-resources-mutation-api\/\">Full-Cycle Reactivity in Angular: Signal Forms, Signal Store, Resources, &#038; Mutation API<\/a><\/li>\n\t\t\t\t\t\t\t<\/ol>\n\t\t<\/div>\n\t<\/div>\n<p>Since the advent of Signals, the NGRX team has been working on a store that leverages this new reactive building block. This new NGRX Signal Store was released with NGRX 17, is fully Signal-based, and highly extensible. <\/p>\n<p>This article shows how to incorporate it in your application. For this, it shows up 3+1 different flavors of using it.<\/p>\n<p>\ud83d\udcc2 <a href=\"https:\/\/github.com\/manfredsteyer\/standalone-example-cli\">Source Code<\/a> <\/p>\n<h2>Getting the Package<\/h2>\n<p>To install the Signal Store, you just need to add the package <code>@ngrx\/signals<\/code> to your application:<\/p>\n<pre><code class=\"language-bash\">npm i @ngrx\/signals<\/code><\/pre>\n<h2>Flavor 1: Lightweight with signalState<\/h2>\n<p>\ud83d\udd00 <em>Branch:<\/em> <code>arc-signal-store<\/code><\/p>\n<p>A very lightweight way of managing Signals with the Signal Store is its <code>signalState<\/code> function (not to be confused with the <code>signalStore<\/code> function). It creates a simple container for managing the passed state using Signals. This container is represented by the type <code>SignalState<\/code>:<\/p>\n<pre><code class=\"language-typescript\">@Injectable({ providedIn: &#039;root&#039; })\n\nimport { signalState } from &#039;@ngrx\/signals&#039;;\n\n[...]\n\nexport class FlightBookingFacade {\n\n    [...]\n\n    private state = signalState({\n        from: &#039;Paris&#039;,\n        to: &#039;London&#039;,\n        preferences: {\n          directConnection: false,\n          maxPrice: 350,\n        },        \n        flights: [] as Flight[],\n        basket: {} as Record&lt;number, boolean&gt;,\n    });\n\n    \/\/ fetch read-only signals\n    flights = this.state.flights;\n    from = this.state.from;\n    to = this.state.to;\n    basket = this.state.basket;\n\n  [...]\n}<\/code><\/pre>\n<p>Each top-level state property gets its own Signal. These properties are retrieved as read-only Signals, ensuring a separation between reading and writing: Consumers using the Signals can just read their values. For updating the state, the service encapsulating the state provides methods (see below). This ensures that the state can only be updated in a well-defined manner.<\/p>\n<p>Also, nested objects like the one provided by the <code>preferences<\/code> property above result in nested signals. Hence, one can retrieve the whole <code>preferences<\/code> object as a Signal but also its properties:<\/p>\n<pre><code class=\"language-typescript\">const ps = this.state.preferences();\nconst direct = this.state.preferences.directConnection();<\/code><\/pre>\n<p>Currently, this isn't implemented for arrays, as Angular's envisioned Signal Components will solve this use case by creating a Signal for each iterated item.  <\/p>\n<h3>Selecting and Computing Signals<\/h3>\n<p>As the Signal Store provides the state as Signals, we can directly use Angular's <code>computed<\/code> function:<\/p>\n<pre><code class=\"language-typescript\">selected = computed(() =&gt;\n  this.flights().filter((f) =&gt; this.basket()[f.id])\n);<\/code><\/pre>\n<p>Here, <code>computed<\/code> serves the same purpose as Selectors in the Redux-based NGRX Store: It<br \/>\nenables us to calculate different state representations for different use cases. These so-called View Models are only recomputed when at least one of the underlying signals changes.<\/p>\n<h3>Updating State<\/h3>\n<p>For updating the <code>SignalState<\/code>, Signal Store provides us with a <code>patchState<\/code> function:<\/p>\n<pre><code class=\"language-typescript\">import { patchState } from &#039;@ngrx\/signals&#039;;\n\n[...]\n\nupdateCriteria(from: string, to: string): void {\n  patchState(this.state, { from, to })\n}<\/code><\/pre>\n<p>Here, we pass in the state container and a partial state. As an alternative, one can pass a function taking the current state and transforming it to the new state:<\/p>\n<pre><code class=\"language-typescript\">updateBasket(id: number, selected: boolean): void {\n  patchState(this.state, state =&gt; ({\n    basket: {\n      ...state.basket,\n      [id]: selected,\n    },\n  }));\n}<\/code><\/pre>\n<h2>Side Effects<\/h2>\n<p>Besides updating the state, methods can also trigger side effects like loading and saving objects:<\/p>\n<pre><code class=\"language-typescript\">async load() {\n  if (!this.from() || !this.to()) return;\n\n  const flights = await this.flightService.findPromise(\n    this.from(),\n    this.to()\n  );\n\n  patchState(this.state, { flights });\n}<\/code><\/pre>\n<h3>Decoupling Intention from Execution<\/h3>\n<p>Sometimes, the caller of <code>patchState<\/code> only knows that some state needs to be updated without knowing where it's located. For such cases, you can provide Updaters. Updaters are just functions taking a current state and returning an updated version of it:<\/p>\n<pre><code class=\"language-typescript\">type BasketSlice = { basket: Record&lt;number, boolean&gt; };\ntype BasketUpdateter = (state: BasketSlice) =&gt; BasketSlice;\n\nexport function updateBasket(flightId: number, selected: boolean): BasketUpdateter {\n  return (state) =&gt; ({\n    ...state,\n    basket: {\n      ...state.basket,\n      [flightId]: selected,\n    },\n  });\n}<\/code><\/pre>\n<p>It's also fine to just return a partial state. It will be patched over the current state:<\/p>\n<pre><code class=\"language-typescript\">type BasketSlice = { basket: Record&lt;number, boolean&gt; };\ntype BasketUpdateter = (state: BasketSlice) =&gt; BasketSlice;\n\nexport function updateBasket(flightId: number, selected: boolean): BasketUpdateter {\n  return (state) =&gt; ({\n    basket: {\n      ...state.basket,\n      [flightId]: selected,\n    },\n  });\n}<\/code><\/pre>\n<p>If you don't need to project the current state, just returning a partial state is fine too. In this case, you can skip the inner function:<\/p>\n<pre><code class=\"language-typescript\">export function updateFlights(flights: Flight[]) {\n  return { flights };\n}<\/code><\/pre>\n<p>Updater can be defined in the <code>Store<\/code>'s (<code>signalState<\/code>'s) &quot;sovereign territory&quot;. For the consumer, it is just a black box:<\/p>\n<pre><code class=\"language-typescript\">patchState(updateBasket(id, selected))<\/code><\/pre>\n<p>Passing an Updater to <code>patchState<\/code> expresses an intention. This is similar to dispatching an Action in the classic NGRX store. However, other than with Redux, no eventing is involved, and we cannot prevent the caller from directly passing their own Updaters. For the latter reason, I'm hiding the <code>SignalStore<\/code> behind a facade.<\/p>\n<h3>More: Angular Architecture Workshop (online, interactive, advanced)<\/h3>\n<p>Become an expert for enterprise-scale and maintainable Angular applications with our <a href=\"https:\/\/www.angulararchitects.io\/en\/angular-workshops\/advanced-angular-enterprise-architecture-incl-ivy\/\">Angular Architecture workshop!<\/a><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2020\/06\/ent-en-1.jpg\" alt=\"\" \/><\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/en\/angular-workshops\/advanced-angular-enterprise-architecture-incl-ivy\/\">All Details (English Workshop)<\/a> | <a href=\"https:\/\/www.angulararchitects.io\/schulungen\/advanced-angular-enterprise-anwendungen-und-architektur\/\">All Details (German Workshop)<\/a><br \/>\n<\/p>\n<h2>Flavor 2: Powerful with signalStore<\/h2>\n<p>\ud83d\udd00 <em>Branch:<\/em> <code>arc-signal-store-2<\/code><\/p>\n<p>Similar to <code>signalState<\/code>, the <code>signalStore<\/code> function creates a container managing state with Signals. However, now, this container is a fully-fledged Store that not only comes with state Signals but also with computed Signals as well as methods for updating the state and for triggering side effects. Hence, there is less need for crafting a facade by hand, as shown above.<\/p>\n<p>Technically, the Store is an Angular service that is composed of several pre-existing features:<\/p>\n<pre><code class=\"language-typescript\">export const FlightBookingStore = signalStore(\n  { providedIn: &#039;root&#039; },\n  withState({\n    from: &#039;Paris&#039;,\n    to: &#039;London&#039;,\n    initialized: false,\n    flights: [] as Flight[],\n    basket: {} as Record&lt;number, boolean&gt;,\n  }),\n\n  \/\/ Activating further features\n  withComputed([...]),\n  withMethods([...]),\n  withHooks([...]),\n)<\/code><\/pre>\n<p>In this case, the service is also registered in the root scope. When skipping <code>{ providedIn: &#039;root&#039; }<\/code>, one needs to register the service by hand, e. g., by providing it when bootstrapping the application, within a router configuration, or on component level.<\/p>\n<h3>Selecting and Computing Signals<\/h3>\n<p>The <code>withComputed<\/code> feature takes the store with its state Signals and defines an object with calculated signals:<\/p>\n<pre><code class=\"language-typescript\">withComputed((store) =&gt; ({\n  selected: computed(() =&gt; store.flights().filter((f) =&gt; store.basket()[f.id])),\n  criteria: computed(() =&gt; ({ from: store.from(), to: store.to() })),\n})),<\/code><\/pre>\n<p>The returned computed signals become part of the store. A more compact version might involve directly destructuring the passed store:<\/p>\n<pre><code class=\"language-typescript\">withComputed(({ flights, basket, from, to }) =&gt; ({\n  selected: selectSignal(() =&gt; flights().filter((f) =&gt; basket()[f.id])),\n  criteria: selectSignal(() =&gt; ({ from: from(), to: to() })),\n})),<\/code><\/pre>\n<h3>Methods for Updating State and Side Effects<\/h3>\n<p>Similar to <code>withComputed<\/code>, <code>withMethods<\/code> also takes the store and returns an object with methods:<\/p>\n<pre><code class=\"language-typescript\">withMethods((state) =&gt; {\n  const { basket, flights, from, to, initialized } = state;\n  const flightService = inject(FlightService);\n\n  return {\n    updateCriteria: (from: string, to: string) =&gt; {\n      patchState(state, { from, to });\n    },\n    updateBasket: (flightId: number, selected: boolean) =&gt; {\n      patchState(state, {\n        basket: {\n          ...basket(),\n          [flightId]: selected,\n        },\n      });\n    },\n    delay: () =&gt; {\n      const currentFlights = flights();\n      const flight = currentFlights[0];\n\n      const date = addMinutes(flight.date, 15);\n      const updFlight = { ...flight, date };\n      const updFlights = [updFlight, ...currentFlights.slice(1)];\n\n      patchState(state, { flights: updFlights });\n    },\n    load: async () =&gt; {\n      if (!from() || !to()) return;\n      const flights = await flightService.findPromise(from(), to());\n      patchState(state, { flights });\n    }     \n  };\n}),<\/code><\/pre>\n<p><code>withMethods<\/code> runs in an injection context and hence can use <code>inject<\/code> to get hold of services. After <code>withMethods<\/code> was executed, the retrieved methods are added to the store.<\/p>\n<h3>Consuming the Store<\/h3>\n<p>From the caller's perspective, the store looks a lot like the facade shown above. We can inject it into a consuming component:<\/p>\n<pre><code class=\"language-typescript\">@Component([...])\nexport class FlightSearchComponent {\n  private store = inject(FlightBookingStore);\n\n  from = this.store.from;\n  to = this.store.to;\n  basket = this.store.basket;\n  flights = this.store.flights;\n  selected = this.store.selected;\n\n  async search() {\n    this.store.load();\n  }\n\n  delay(): void {\n    this.store.delay();\n  }\n\n  updateCriteria(from: string, to: string): void {\n    this.store.updateCriteria(from, to);\n  }\n\n  updateBasket(id: number, selected: boolean): void {\n    this.store.updateBasket(id, selected);\n  }\n}<\/code><\/pre>\n<h3>Hooks<\/h3>\n<p>The function <code>withHooks<\/code> provides another feature allowing to setup lifecycle hooks to run when the store is initialized or destroyed:<\/p>\n<pre><code class=\"language-typescript\">withHooks({\n  onInit({ load }) {\n    load()\n  },\n  onDestroy({ flights }) {\n    console.log(&#039;flights are destroyed now&#039;, flights());\n  },\n}),<\/code><\/pre>\n<p>Both hooks get the store passed. One more time, by using destructuring, you can focus on a subset of the stores members.<\/p>\n<h3>rxMethod<\/h3>\n<p>\ud83d\udd00 <em>Branch:<\/em> <code>arc-signal-store-rx<\/code><\/p>\n<p>While Signals are easy to use, they are not a full replacement for RxJS. For leveraging RxJS and its powerful operators, the Signal Store provides a secondary entry point <code>@ngrx\/signals\/rxjs-interop<\/code>, containing a function <code>rxMethod&lt;T&gt;<\/code>. It allows working with an Observable representing side-effects that automatically run when specific values change:<\/p>\n<pre><code class=\"language-typescript\">import { rxMethod } from &#039;@ngrx\/signals\/rxjs-interop&#039;;\n\n[...]\n\nwithMethods(({ $update, basket, flights, from, to, initialized }) =&gt; {\n  const flightService = inject(FlightService);\n\n  return {\n    [...]\n    connectCriteria: rxMethod&lt;Criteria&gt;((c$) =&gt; c$.pipe(\n      filter(c =&gt; c.from.length &gt;= 3 &amp;&amp; c.to.length &gt;= 3),\n      debounceTime(300),\n      switchMap((c) =&gt; flightService.find(c.from, c.to)),\n      tap(flights =&gt; patchState(state, { flights }))\n    ))\n  }\n});<\/code><\/pre>\n<p>The type parameter <code>T<\/code> defines the type the <code>rxMethod<\/code> works on. While the <code>rxMethod<\/code> receives an <code>Obserable&lt;T&gt;<\/code>, the caller can also pass an <code>Observable&lt;T&gt;<\/code>, a <code>Signal&lt;T&gt;<\/code>, or <code>T<\/code> directly. In the latter two cases, the passed values are converted into an Observable.<\/p>\n<p>After defining the <code>rxMethod<\/code>, somewhere else in the application, e. g. in a hook or a regular method, you can call this effect:<\/p>\n<pre><code class=\"language-typescript\">withHooks({\n  onInit({ loadBy, criteria }) {\n    connectCriteria(criteria());\n  },\n})<\/code><\/pre>\n<p>Here, the criteria Signal -- a computed signal -- is passed. Every time this Signal changes, the effect within <code>connectCriteria<\/code> is re-executed.<\/p>\n<h2>Custom Features - The Road Towards Further Flavors<\/h2>\n<p>\ud83d\udd00 <em>Branch:<\/em> <code>arc-signal-store-custom<\/code><\/p>\n<p>Besides configuring the Store with baked-in features, everyone can write their own features to automate repeating tasks. The playground provided by <a href=\"https:\/\/twitter.com\/MarkoStDev\">Marko Stanimirovi\u0107<\/a>, the NGRX contributor behind the Signal Store, contains several examples of such features.<\/p>\n<p>One of the examples found in this repository is a <a href=\"https:\/\/github.com\/markostanimirovic\/ngrx-signal-store-playground\/blob\/main\/src\/app\/shared\/call-state.feature.ts\">CallState feature<\/a> defining a state property informing about the state of the current HTTP call:<\/p>\n<pre><code class=\"language-typescript\">export type CallState = &#039;init&#039; | &#039;loading&#039; | &#039;loaded&#039; | { error: string };<\/code><\/pre>\n<p>In this section, I'm using this example to explain how to provide custom features.<\/p>\n<h3>Defining Custom Features<\/h3>\n<p>A feature is usually created by calling <code>signalStoreFeature<\/code>. This function constructs a new feature on top of existing ones.<\/p>\n<pre><code class=\"language-typescript\">\/\/ Taken from: https:\/\/github.com\/markostanimirovic\/ngrx-signal-store-playground\/blob\/main\/src\/app\/shared\/call-state.feature.ts\n\nimport { computed } from &#039;@angular\/core&#039;;\nimport {\n  signalStoreFeature,\n  withComputed,\n  withState,\n} from &#039;@ngrx\/signals&#039;;\n\nexport type CallState = &#039;init&#039; | &#039;loading&#039; | &#039;loaded&#039; | { error: string };\n\nexport function withCallState() {\n  return signalStoreFeature(\n    withState&lt;{ callState: CallState }&gt;({ callState: &#039;init&#039; }),\n    withComputed(({ callState }) =&gt; ({\n      loading: computed(() =&gt; callState() === &#039;loading&#039;),\n      loaded: computed(() =&gt; callState() === &#039;loaded&#039;),\n      error: computed(() =&gt; {\n        const state = callState();\n        return typeof state === &#039;object&#039; ? state.error : null\n      }),\n    }))\n  );\n}<\/code><\/pre>\n<p>For the state properties added by the feature, one can provide <code>Updaters<\/code>:<\/p>\n<pre><code class=\"language-typescript\">export function setLoading(): { callState: CallState } {\n  return { callState: &#039;loading&#039; };\n}\n\nexport function setLoaded(): { callState: CallState } {\n  return { callState: &#039;loaded&#039; };\n}\n\nexport function setError(error: string): { callState: CallState } {\n  return { callState: { error } };\n}<\/code><\/pre>\n<p>Updaters allows the consumer to modify the feature state without actually knowing how it's structured. <\/p>\n<h3>Using Custom Features<\/h3>\n<p>For using Custom Features, just call the provided factory when setting up the store:<\/p>\n<pre><code class=\"language-typescript\">export const FlightBookingStore = signalStore(\n  { providedIn: &#039;root&#039; },\n  withState({ [...] }),\n\n  \/\/ Add feature:\n  withCallState(),\n  [...]\n\n  withMethods([...])\n  [...]\n);<\/code><\/pre>\n<p>The provided properties, methods, and Updaters can be used in the Store's methods:<\/p>\n<pre><code class=\"language-typescript\">load: async () =&gt; {\n  if (!from() || !to()) return;\n\n  \/\/ Setting the callState via an Updater\n  patchState(state, setLoading());\n\n  const flights = await flightService.findPromise(from(), to());\n  patchState(state, { flights });\n\n  \/\/ Setting the callState via an Updater\n  patchState(state, setLoaded());\n},<\/code><\/pre>\n<p>The consumer of the store sees the properties provided by the feature too:<\/p>\n<pre><code class=\"language-typescript\">private store = inject(FlightBookingStore);\n\nflights = this.store.flightEntities;\nloading = this.store.loading;<\/code><\/pre>\n<p>As each feature is transforming the Store's properties and methods, make sure to call them in the right order. If we assume that methods registered with <code>withMethods<\/code> use the <code>CallState<\/code>, <code>withCallState<\/code> has to be called before <code>withMethods<\/code>.<\/p>\n<h2>Flavor 3: Built-in Features like Entity Management<\/h2>\n<p>\ud83d\udd00 <em>Branch:<\/em> <code>arc-signal-store-entities<\/code><\/p>\n<p>The NGRX Signal Store already comes with a convenient extension for managing entities. It can be found in the secondary entry point <code>@ngrx\/signals\/entities<\/code> and provides data structures for entities but also several Updaters, e. g., for inserting entities or for updating a single entity by id.<\/p>\n<p>To setup entity management, just call the <code>withEntities<\/code> function:<\/p>\n<pre><code class=\"language-typescript\">import { withEntities } from &#039;@ngrx\/signals\/entities&#039;;\n\nconst BooksStore = signalStore(\n  [...]\n\n  \/\/ Defining an Entity\n  withEntities({ entity: type&lt;Flight&gt;(), collection: &#039;flight&#039; }),\n\n  \/\/ withEntities created a flightEntities signal for us:\n  withComputed(({ flightEntities, basket, from, to }) =&gt; ({\n    selected: computed(() =&gt; flightEntities().filter((f) =&gt; basket()[f.id])),\n    criteria: computed(() =&gt; ({ from: from(), to: to() })),\n  })),\n\n  withMethods((state) =&gt; {\n    const { basket, flightEntities, from, to, initialized } = state;\n    const flightService = inject(FlightService);\n\n    return {\n      [...],\n\n      load: async () =&gt; {\n        if (!from() || !to()) return;\n        patchState(state, setLoading());\n\n        const flights = await flightService.findPromise(from(), to());\n\n        \/\/ Updating entities with out-of-the-box setAllEntities Updater\n        patchState(state, setAllEntities(flights, { collection: &#039;flight&#039; }));\n        patchState(state, setLoaded());\n      },\n\n      [...],\n    };\n  }),\n);<\/code><\/pre>\n<p>The passed collection name prevents naming conflicts. In our case, the collection is called <code>flight<\/code>, and hence the feature creates several properties beginning with <code>flight<\/code>, e.g., <code>flightEntities<\/code>.<\/p>\n<p>There is quite an amount of ready-to-use Updaters: <\/p>\n<ul>\n<li><code>addEntity<\/code><\/li>\n<li><code>addEntities<\/code><\/li>\n<li><code>removeEntity<\/code><\/li>\n<li><code>removeEntities<\/code><\/li>\n<li><code>removeAllEntities<\/code><\/li>\n<li><code>setEntity<\/code><\/li>\n<li><code>setEntities<\/code><\/li>\n<li><code>setAllEntities<\/code><\/li>\n<li><code>updateEntity<\/code><\/li>\n<li><code>updateEntities<\/code><\/li>\n<li><code>updateAllEntities<\/code><\/li>\n<\/ul>\n<p>Similar to <code>@ngrx\/entities<\/code>, internally, the entities are stored in a normalized way. That means they are stored in a dictionary, mapping their primary keys to the entity objects. This makes it easier to join them together to View Models needed for specific use cases. <\/p>\n<p>As we call our collection <code>flight<\/code>, <code>withEntities<\/code> creates a Signal <code>flightEntityMap<\/code> mapping flight ids to our flight objects. Also, it creates a Signal <code>flightIds<\/code> containing all the ids in the order. Both are used by the also added computed signal <code>flightEntities<\/code> used above. It returns all the flights as an array respecting the order of the ids within <code>flightIds<\/code>. Hence, if you want to rearrange the positions of our flights, just update the <code>flightIds<\/code> property accordingly.<\/p>\n<p>For building the structures like the <code>flightEntityMap<\/code>, the Updaters need to know how the entity's id is called. By default, it assumes a property <code>id<\/code>. If the id is called differently, you can tell the Updater by using the <code>idKey<\/code> property:<\/p>\n<pre><code class=\"language-typescript\">patchState(state, setAllEntities(flights, { collection: &#039;flight&#039;, idKey: &#039;flightId&#039; }));<\/code><\/pre>\n<p>The passed property needs to be a <code>string<\/code> or <code>number<\/code>. If it's of a different data type or if it doesn't exist at all, you get a compilation error.<\/p>\n<h2>Conclusion<\/h2>\n<p>The upcoming NGRX Signal Store allows managing state using Signals. The most lightweight option for using this library is just to go with a <code>SignalState<\/code> container. This data structure provides a Signal for each state property. These signals are read-only. For updating the state, you can use the <code>patchState<\/code> function. To make sure updates only happen in a well-defined way, the <code>signalState<\/code> can be hidden behind a facade.<\/p>\n<p>The <code>SignalStore<\/code> is more powerful and allows to register optional features. They define the state to manage but also methods operating on it. A <code>SignalStore<\/code> can be provided as a service and injected into its consumers.<\/p>\n<p>The <code>SignalStore<\/code> also provides an extension mechanism for implementing custom features to ease repeating tasks. Out of the box, the Signal Store comes with a pretty handy feature for managing entities. <\/p>\n<h2>What's next? More on Architecture!<\/h2>\n<p>Please find more information on enterprise-scale Angular architectures in our free eBook (5th edition, 12 chapters):<\/p>\n<ul>\n<li>According to which criteria can we subdivide a huge application into sub-domains?<\/li>\n<li>How can we make sure, the solution is maintainable for years or even decades?<\/li>\n<li>Which options from Micro Frontends are provided by Module Federation?<\/li>\n<\/ul>\n<p><a href=\"https:\/\/www.angulararchitects.io\/book\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2022\/12\/cover-5th-small.png\" alt=\"free\" \/><\/a><\/p>\n<p>Feel free to <a href=\"https:\/\/www.angulararchitects.io\/en\/book\">download it here<\/a> now!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The NGRX team has been working on a store that fully leverages Signals. It&#8217;s lightweight and extensible.<\/p>\n","protected":false},"author":9,"featured_media":7295,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_price":"","_stock":"","_tribe_ticket_header":"","_tribe_default_ticket_provider":"","_ticket_start_date":"","_ticket_end_date":"","_tribe_ticket_show_description":"","_tribe_ticket_show_not_going":false,"_tribe_ticket_use_global_stock":"","_tribe_ticket_global_stock_level":"","_global_stock_mode":"","_global_stock_cap":"","_tribe_rsvp_for_event":"","_tribe_ticket_going_count":"","_tribe_ticket_not_going_count":"","_tribe_tickets_list":"[]","_tribe_ticket_has_attendee_info_fields":false,"footnotes":""},"categories":[18],"tags":[],"class_list":["post-7292","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","post_series-ngrx-signal-store-en"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>The new NGRX Signal Store for Angular: 3+n Flavors - ANGULARarchitects<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"The new NGRX Signal Store for Angular: 3+n Flavors - ANGULARarchitects\" \/>\n<meta property=\"og:description\" content=\"The NGRX team has been working on a store that fully leverages Signals. It&#039;s lightweight and extensible.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2023-07-05T11:30:50+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-12-08T12:07:03+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/sm-3.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1000\" \/>\n\t<meta property=\"og:image:height\" content=\"500\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Manfred Steyer, GDE\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/sm-3.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@daniel\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Manfred Steyer, GDE\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/\"},\"author\":{\"name\":\"Manfred Steyer, GDE\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/15628efa7af4475ffaaeeb26c5112951\"},\"headline\":\"The new NGRX Signal Store for Angular: 3+n Flavors\",\"datePublished\":\"2023-07-05T11:30:50+00:00\",\"dateModified\":\"2023-12-08T12:07:03+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/\"},\"wordCount\":1625,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/shutterstock-558977833.jpg\",\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/\",\"name\":\"The new NGRX Signal Store for Angular: 3+n Flavors - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/shutterstock-558977833.jpg\",\"datePublished\":\"2023-07-05T11:30:50+00:00\",\"dateModified\":\"2023-12-08T12:07:03+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/shutterstock-558977833.jpg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/shutterstock-558977833.jpg\",\"width\":1000,\"height\":667},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"The new NGRX Signal Store for Angular: 3+n Flavors\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"name\":\"ANGULARarchitects\",\"description\":\"AngularArchitects.io\",\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\",\"name\":\"ANGULARarchitects\",\"alternateName\":\"SOFTWAREarchitects\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"width\":644,\"height\":216,\"caption\":\"ANGULARarchitects\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/github.com\/angular-architects\",\"https:\/\/www.linkedin.com\/company\/angular-architects\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/15628efa7af4475ffaaeeb26c5112951\",\"name\":\"Manfred Steyer, GDE\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/a0b59539674d8b71ea1c1f4764b11244b5f499203f1d11b40f37d8f3f90be033?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/a0b59539674d8b71ea1c1f4764b11244b5f499203f1d11b40f37d8f3f90be033?s=96&d=mm&r=g\",\"caption\":\"Manfred Steyer, GDE\"},\"sameAs\":[\"https:\/\/x.com\/daniel\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"The new NGRX Signal Store for Angular: 3+n Flavors - ANGULARarchitects","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/","og_locale":"en_US","og_type":"article","og_title":"The new NGRX Signal Store for Angular: 3+n Flavors - ANGULARarchitects","og_description":"The NGRX team has been working on a store that fully leverages Signals. It's lightweight and extensible.","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/","og_site_name":"ANGULARarchitects","article_published_time":"2023-07-05T11:30:50+00:00","article_modified_time":"2023-12-08T12:07:03+00:00","og_image":[{"width":1000,"height":500,"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/sm-3.jpg","type":"image\/jpeg"}],"author":"Manfred Steyer, GDE","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/sm-3.jpg","twitter_creator":"@daniel","twitter_misc":{"Written by":"Manfred Steyer, GDE","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/"},"author":{"name":"Manfred Steyer, GDE","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/15628efa7af4475ffaaeeb26c5112951"},"headline":"The new NGRX Signal Store for Angular: 3+n Flavors","datePublished":"2023-07-05T11:30:50+00:00","dateModified":"2023-12-08T12:07:03+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/"},"wordCount":1625,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/shutterstock-558977833.jpg","inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/","name":"The new NGRX Signal Store for Angular: 3+n Flavors - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/shutterstock-558977833.jpg","datePublished":"2023-07-05T11:30:50+00:00","dateModified":"2023-12-08T12:07:03+00:00","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/shutterstock-558977833.jpg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/shutterstock-558977833.jpg","width":1000,"height":667},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"The new NGRX Signal Store for Angular: 3+n Flavors"}]},{"@type":"WebSite","@id":"https:\/\/www.angulararchitects.io\/en\/#website","url":"https:\/\/www.angulararchitects.io\/en\/","name":"ANGULARarchitects","description":"AngularArchitects.io","publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.angulararchitects.io\/en\/#organization","name":"ANGULARarchitects","alternateName":"SOFTWAREarchitects","url":"https:\/\/www.angulararchitects.io\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","width":644,"height":216,"caption":"ANGULARarchitects"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/github.com\/angular-architects","https:\/\/www.linkedin.com\/company\/angular-architects\/"]},{"@type":"Person","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/15628efa7af4475ffaaeeb26c5112951","name":"Manfred Steyer, GDE","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/a0b59539674d8b71ea1c1f4764b11244b5f499203f1d11b40f37d8f3f90be033?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a0b59539674d8b71ea1c1f4764b11244b5f499203f1d11b40f37d8f3f90be033?s=96&d=mm&r=g","caption":"Manfred Steyer, GDE"},"sameAs":["https:\/\/x.com\/daniel"]}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/7292","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=7292"}],"version-history":[{"count":8,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/7292\/revisions"}],"predecessor-version":[{"id":23740,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/7292\/revisions\/23740"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/7295"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=7292"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=7292"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=7292"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}