{"id":23591,"date":"2023-11-27T00:02:17","date_gmt":"2023-11-26T23:02:17","guid":{"rendered":"https:\/\/www.angulararchitects.io\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/"},"modified":"2023-12-11T17:54:38","modified_gmt":"2023-12-11T16:54:38","slug":"smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/","title":{"rendered":"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features"},"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-en6a2b4c8ac263c\" 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-en6a2b4c8ac263c\"\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 2 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><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/the-new-ngrx-signal-store-for-angular-2-1-flavors\/\">The new NGRX Signal Store for Angular: 3+n Flavors<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><span class=\"wp-post-series-box__current\">Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features<\/span><\/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>What would you say if you could implement a Signal Store for a (repeating) <strong>CRUD use case<\/strong> including <strong>Undo\/Redo<\/strong> in <strong>just 7 (!) lines of code?<\/strong><\/p>\n<p>To make this possible, we need some custom features for the Signal Store. In this article, I show how this all works.<\/p>\n<p>As always, my work is highly inspired by the implementation of the NGRX Signal and the examples provided by <a href=\"https:\/\/twitter.com\/MarkoStDev\">Marko Stanimirovi\u0107<\/a>, the NGRX core team member who envisioned and implemented the Signal Store.<\/p>\n<p>\ud83d\udcc2 <a href=\"https:\/\/github.com\/manfredsteyer\/standalone-example-cli\/tree\/arc-signal-store-custom-examples\">Source Code<\/a> (Branch: arc-signal-store-custom-examples)<\/p>\n<h2>Goal<\/h2>\n<p>The goal of this article is to show how to implement custom features for the Signal Store that allow for the following:<\/p>\n<ul>\n<li>Searching for entities<\/li>\n<li>Selecting several entities<\/li>\n<li>Displaying the selected entities<\/li>\n<li>Undo\/Redo<\/li>\n<\/ul>\n<p>This is how the demo application I've built on top of these custom features looks like:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/result.png\" alt=\"Demo Application\" \/><\/p>\n<p>And this is the whole code we need to set up the store, including Undo\/Redo and connecting it to a Data Service fetching the entities from the backend:<\/p>\n<pre><code class=\"language-typescript\">export const FlightBookingStore = signalStore(\n  { providedIn: &#039;root&#039; },\n  withEntities&lt;Flight&gt;(),\n  withCallState(),\n  withDataService(FlightService, { from: &#039;Graz&#039;, to: &#039;Hamburg&#039;} ),\n  withUndoRedo(),\n);<\/code><\/pre>\n<p>As you can see, I'm using the <code>@ngrx\/signals\/entities<\/code> package for managing entities. Besides this, I moved the remaining logic into three reusable custom features: <code>withCallState<\/code> was already discussed in a previous article. This article discusses <code>withDataService<\/code> and provides the code for <code>withUndoRedo<\/code>. <\/p>\n<h2>DataService Custom Feature<\/h2>\n<p>The idea behind the <code>DataService<\/code> feature is to set up state for a search filter and to connect an Angular Service that uses this filter to search for entities. In a further development stage, the feature could also call the <code>DataService<\/code> for saving and deleting entities. However, as these implementations would not add additional insights here, I decided to skip them for the sake of brevity. <\/p>\n<p>To make the <code>DataService<\/code> feature generic, we need some general types describing everything the feature interacts with:<\/p>\n<pre><code class=\"language-typescript\">import { EntityId } from &quot;@ngrx\/signals\/entities&quot;;\n[...]\n\nexport type Filter = Record&lt;string, unknown&gt;;\nexport type Entity = { id: EntityId };\n\nexport interface DataService&lt;E extends Entity, F extends Filter&gt; {\n    load(filter: F): Promise&lt;E[]&gt;;\n}<\/code><\/pre>\n<p>These types describe how our search filter is structured, what we mean when referring to an entity, and how a <code>DataService<\/code> should look like. The type <code>EntityId<\/code> comes from <code>@ngrx\/signals\/entities<\/code> and accepts a <code>string<\/code> or a <code>number<\/code>.<\/p>\n<p>Expecting that an entity is an arbitrary object with an <code>id<\/code> property is one of the conventions <code>@ngrx\/signals\/entities<\/code> provides for shorten your code. If your primary key is called otherwise, you can tell <code>@ngrx\/signals\/entities<\/code> accordingly. However, to keep the presented example small, I've decided to stick with this convention.<\/p>\n<h2>More: Angular Architecture Workshop (online, interactive, advanced)<\/h2>\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>Implementing A Generic Custom Feature<\/h2>\n<p>The function <code>withDataService<\/code> returns the <code>DataService<\/code> feature:<\/p>\n<pre><code class=\"language-typescript\">export function withDataService&lt;E extends Entity, F extends Filter, S extends DataService&lt;E, F&gt;&gt;(dataServiceType: Type&lt;S&gt;, filter: F) {\n    [...]\n}<\/code><\/pre>\n<p>Its type parameter describes the Entity to manage, the corresponding search filter, and the <code>DataService<\/code>. When calling this generic method we just need to pass in the <code>DataService<\/code> and an initial filter. TypeScript infers the rest:<\/p>\n<pre><code class=\"language-typescript\">withDataService(FlightService, { from: &#039;Graz&#039;, to: &#039;Hamburg&#039;} ),<\/code><\/pre>\n<p>The <code>withDataService<\/code> function calls <code>signalStoreFeature<\/code> to setup our custom feature:<\/p>\n<pre><code class=\"language-typescript\">export function withDataService&lt;E extends Entity, F extends Filter, S extends DataService&lt;E, F&gt;&gt;(dataServiceType: Type&lt;S&gt;, filter: F) {\n    return signalStoreFeature(\n        \/\/ Our expectations to the store:\n        {\n            state: type&lt;{\n                callState: CallState,\n                entityMap: Record&lt;EntityId, E&gt;,\n                ids: EntityId[]\n            }&gt;(),\n            signals: type&lt;{\n                entities: Signal&lt;Entity[]&gt;\n            }&gt;(),\n            methods: type&lt;{}&gt;()\n        },\n\n        \/\/ Composing several features:\n        withState( [...] ),\n        withComputed( [...] ),\n        withMethods( [...] )\n    );\n}<\/code><\/pre>\n<p>As shown in the first article of this series, the <code>signalStoreFeature<\/code> function basically composes existing features into a new one. For instance, we can introduce new state properties with <code>withState<\/code>, computed Signals with <code>withComputed<\/code>, or methods with <code>withMethods<\/code>. <\/p>\n<p>However, one little thing is a bit different this time: Our feature has some <strong>expectations<\/strong> for the Signal Store it is used with. It expects the <code>callState<\/code> feature and the <code>entity<\/code> feature to be in place. The former one sets up a <code>callState<\/code> property we need; the latter one sets up an <code>entityMap<\/code> and an <code>ids<\/code> property as well as a calculated Signal <code>entities<\/code>.<\/p>\n<p>These expectations are defined by the first parameter passed to <code>signalStoreFeature<\/code>. It describes the expected state properties (<code>state<\/code>), computed signals (<code>signals<\/code>), and methods. As we don't expect any methods, we can also omit the key <code>methods<\/code> instead of pointing to <code>type&lt;{}&gt;()<\/code>.<\/p>\n<p>To avoid naming conflicts, the <code>entity<\/code> feature allows using different property names. To keep things simple, I'm sticking with the default names here. However, in a further article of this series, you learn how to deal with dynamic property names in a type-safe way. <\/p>\n<p>The remaining parts of this custom feature are just about adding state properties, computed Signals, and methods on top of the expected features:<\/p>\n<pre><code class=\"language-typescript\">export function withDataService&lt;E extends Entity, F extends Filter, S extends DataService&lt;E, F&gt;&gt;(dataServiceType: Type&lt;S&gt;, filter: F) {\n    return signalStoreFeature(\n        \/\/ First parameter contains \n        \/\/ Our expectations to the store:\n        \/\/ If they are not fulfilled, TypeScript\n        \/\/ will prevent adding this feature!\n        {\n            state: type&lt;{\n                callState: CallState,\n                entityMap: Record&lt;EntityId, E&gt;,\n                ids: EntityId[]\n            }&gt;(),\n            signals: type&lt;{\n                entities: Signal&lt;Entity[]&gt;\n            }&gt;(),\n            methods: type&lt;{}&gt;()\n        },\n        withState({\n            filter,\n            selectedIds: {} as Record&lt;EntityId, boolean&gt;,\n        }),\n        withComputed(({ selectedIds, entities }) =&gt; ({\n            selectedEntities: computed(() =&gt; entities().filter(e =&gt; selectedIds()[e.id]))\n        })),\n        withMethods((store) =&gt; {\n            const dataService = inject(dataServiceType)\n            return {\n                updateFilter(filter: F): void {\n                    patchState(store, { filter });\n                },\n                updateSelected(id: EntityId, selected: boolean): void {\n                    patchState(store, ({ selectedIds }) =&gt; ({\n                        selectedIds: {\n                            ...selectedIds,\n                            [id]: selected,\n                        }\n                    }));\n                },\n                async load(): Promise&lt;void&gt; {\n                    patchState(store, setLoading());\n                    const result = await dataService.load(store.filter());\n                    patchState(store, setAllEntities(result));\n                    patchState(store, setLoaded());\n                }\n            };\n        })\n    );\n}<\/code><\/pre>\n<h2>Providing a Fitting Data Service<\/h2>\n<p>To make our data services work with our custom feature, they need to implement the above-mentioned <code>DataService<\/code> interface that is to be typed with the Entity in question and a search filter expected by the <code>load<\/code> method:<\/p>\n<pre><code class=\"language-typescript\">export type FlightFilter = { \n    from: string; \n    to: string; \n}\n\n@Injectable({\n  providedIn: &#039;root&#039;\n})\nexport class FlightService implements DataService&lt;Flight, FlightFilter&gt; {\n  baseUrl = `https:\/\/demo.angulararchitects.io\/api`;\n\n  constructor(private http: HttpClient) {}\n\n  load(filter: FlightFilter): Promise&lt;Flight[]&gt; {\n    [...]\n  }\n\n  [...]\n}<\/code><\/pre>\n<h2>Undo\/Redo-Feature<\/h2>\n<p>The Undo\/Redo feature is implemented in a very similar way. Internally, it managed two stacks: an undo stack and a redo stack. The stacks are basically arrays with <code>StackItem<\/code>s:<\/p>\n<pre><code class=\"language-typescript\">export type StackItem = {\n    filter: Filter;\n    entityMap: Record&lt;EntityId, Entity&gt;,\n    ids: EntityId[]\n};<\/code><\/pre>\n<p>Each <code>StackItem<\/code> represents a snapshot of the current search filter and the information the entity feature uses (<code>entityMap<\/code>, <code>ids<\/code>). <\/p>\n<p>For configuring the feature, a <code>UndoRedoOptions<\/code> type is used:<\/p>\n<pre><code class=\"language-typescript\">export type UndoRedoOptions = {\n    maxStackSize: number;\n}\n\nexport const defaultUndoRedoOptions: UndoRedoOptions = {\n    maxStackSize: 100\n}<\/code><\/pre>\n<p>The options object allows us to limit the stack size. Older items are removed according to the First In, First Out rule if the stack grows to large.<\/p>\n<p>The <code>withUndoRedo<\/code> function adds the feature. It is structured as follows:<\/p>\n<pre><code class=\"language-typescript\">export function withUndoRedo(options = defaultUndoRedoOptions) {\n\n    let previous: StackItem | null = null;\n    let skipOnce = false;\n\n    const undoStack: StackItem[] = [];\n    const redoStack: StackItem[] = [];\n\n    [...]\n\n    return signalStoreFeature(\n        \/\/ Expectations to the store:\n        {\n            state: type&lt;{\n                filter: Filter,\n                entityMap: Record&lt;EntityId, Entity&gt;,\n                ids: EntityId[]\n            }&gt;(),\n        },\n        [...]\n        withMethods((store) =&gt; ({\n            undo(): void { [...] },\n            redo(): void { [...] }\n        })),\n        withHooks({\n            onInit(store) {\n                effect(() =&gt; {\n                    const filter = store.filter();\n                    const entityMap = store.entityMap();\n                    const ids = store.ids();\n\n                    [...]\n                });\n            }\n        })\n\n    )\n}<\/code><\/pre>\n<p>Similar to the <code>withDataService<\/code> function discussed above, it calls <code>signalStoreFeature<\/code> and defines its expectations for the store using the first argument. It introduces an <code>undo<\/code> and a <code>redo<\/code> method, restoring the state from the respective stacks. To observe the state, the <code>onInit<\/code> hook at the end creates an effect. After each change, this effect stores the original state on the undo stack. <\/p>\n<p>One thing is a bit special about this implementation of the Undo\/Redo feature: The feature itself holds some internal state -- like the <code>undoStack<\/code> and the <code>redoStack<\/code> -- that is not part of the Signal Store. <\/p>\n<p>Please find the full implementation of this feature in my \ud83d\udcc2 <a href=\"https:\/\/github.com\/manfredsteyer\/standalone-example-cli\/tree\/arc-signal-store-custom-examples\">GitHub repository<\/a> (\ud83d\udd00 Branch: arc-signal-store-custom-examples). If you want to see a different implementation that also stores the feature-internal state in the Signal Store, please look at the \ud83d\udd00 <code>arc-signal-custom-examples-undoredo-alternative<\/code> branch.<\/p>\n<h2>Using the Store in a Component<\/h2>\n<p>To use our 7-lines-of-code-signal-store in a component, just inject it and delegate to its signals and methods:<\/p>\n<pre><code class=\"language-typescript\">@Component( [...] )\nexport class FlightSearchComponent {\n  private store = inject(FlightBookingStore);\n\n  \/\/ Delegate to signals\n  from = this.store.filter.from;\n  to = this.store.filter.to;\n  flights = this.store.entities;\n  selected = this.store.selectedEntities;\n  selectedIds = this.store.selectedIds;\n\n  \/\/ Delegate to methods\n  async search() {\n    this.store.load();\n  }\n\n  undo(): void {\n    this.store.undo();\n  }\n\n  redo(): void {\n    this.store.redo();\n  }\n\n  updateCriteria(from: string, to: string): void {\n    this.store.updateFilter({ from, to });\n  }\n\n  updateBasket(id: number, selected: boolean): void {\n    this.store.updateSelected(id, selected);\n  }\n\n}<\/code><\/pre>\n<h2>Conclusion and Outlook<\/h2>\n<p>Implementing repeating tasks with generic custom features allows you to shrink down your source code dramatically. In this article, we implemented a Signal Store for a simple use case with just 7 lines of code. While implementing such features in a generic way adds some overhead in the first place, this effort pays off for sure once you have several use cases structured that way. <\/p>\n<p>To reuse existing custom features, our custom feature delegates to them. The API provided by the NGRX Signal Store allows the custom feature to ensure the other features have been configured. It defines which state properties, computed signals, and methods it expects. If they are not present, TypeScript brings up a compilation error.<\/p>\n<p>For the sake of simplicity, we just went with the default property names introduced by the orchestrated features. However, to avoid naming conflicts, it is also possible to configure these names. For instance, the <code>entity<\/code> feature that ships with the Signal Store supports such dynamic properties without compromising type safety. In the next article, I show how to use this idea for our custom features, too.<\/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>This is post 2 of 7 in the series &ldquo;NGRX Signal Store&rdquo; The new NGRX Signal Store for Angular: 3+n Flavors Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features NGRX Signal Store Deep Dive: Flexible and Type-Safe Custom Extensions The NGRX Signal Store and Your Architecture Using Angular&#8217;s Resource API [&hellip;]<\/p>\n","protected":false},"author":25,"featured_media":23589,"comment_status":"open","ping_status":"open","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-23591","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>Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features - 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\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features - ANGULARarchitects\" \/>\n<meta property=\"og:description\" content=\"This is post 2 of 7 in the series &ldquo;NGRX Signal Store&rdquo; The new NGRX Signal Store for Angular: 3+n Flavors Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features NGRX Signal Store Deep Dive: Flexible and Type-Safe Custom Extensions The NGRX Signal Store and Your Architecture Using Angular&#8217;s Resource API [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2023-11-26T23:02:17+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-12-11T16:54:38+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/sujet-2.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\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/sujet-2.jpg\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Manfred Steyer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 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\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/\"},\"author\":{\"name\":\"Manfred Steyer\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\"},\"headline\":\"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features\",\"datePublished\":\"2023-11-26T23:02:17+00:00\",\"dateModified\":\"2023-12-11T16:54:38+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/\"},\"wordCount\":1191,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/shutterstock_1943371324.jpg\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/\",\"name\":\"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/shutterstock_1943371324.jpg\",\"datePublished\":\"2023-11-26T23:02:17+00:00\",\"dateModified\":\"2023-12-11T16:54:38+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/shutterstock_1943371324.jpg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/shutterstock_1943371324.jpg\",\"width\":1000,\"height\":652},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features\"}]},{\"@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\/f3de69c1e2bdb5ba04d8d2f5f998b81a\",\"name\":\"Manfred Steyer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"caption\":\"Manfred Steyer\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features - 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\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/","og_locale":"en_US","og_type":"article","og_title":"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features - ANGULARarchitects","og_description":"This is post 2 of 7 in the series &ldquo;NGRX Signal Store&rdquo; The new NGRX Signal Store for Angular: 3+n Flavors Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features NGRX Signal Store Deep Dive: Flexible and Type-Safe Custom Extensions The NGRX Signal Store and Your Architecture Using Angular&#8217;s Resource API [&hellip;]","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/","og_site_name":"ANGULARarchitects","article_published_time":"2023-11-26T23:02:17+00:00","article_modified_time":"2023-12-11T16:54:38+00:00","og_image":[{"width":1000,"height":500,"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/sujet-2.jpg","type":"image\/jpeg"}],"author":"Manfred Steyer","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/sujet-2.jpg","twitter_misc":{"Written by":"Manfred Steyer","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/"},"author":{"name":"Manfred Steyer","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a"},"headline":"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features","datePublished":"2023-11-26T23:02:17+00:00","dateModified":"2023-12-11T16:54:38+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/"},"wordCount":1191,"commentCount":0,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/shutterstock_1943371324.jpg","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/","name":"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/shutterstock_1943371324.jpg","datePublished":"2023-11-26T23:02:17+00:00","dateModified":"2023-12-11T16:54:38+00:00","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/shutterstock_1943371324.jpg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/11\/shutterstock_1943371324.jpg","width":1000,"height":652},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/smarter-not-harder-simplifying-your-application-with-ngrx-signal-store-and-custom-features\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"Smarter, Not Harder: Simplifying your Application With NGRX Signal Store and Custom Features"}]},{"@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\/f3de69c1e2bdb5ba04d8d2f5f998b81a","name":"Manfred Steyer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","caption":"Manfred Steyer"}}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/23591","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\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=23591"}],"version-history":[{"count":10,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/23591\/revisions"}],"predecessor-version":[{"id":23767,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/23591\/revisions\/23767"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/23589"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=23591"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=23591"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=23591"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}