惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
T
The Blog of Author Tim Ferriss
Recent Announcements
Recent Announcements
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
MongoDB | Blog
MongoDB | Blog
U
Unit 42
B
Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
P
Privacy International News Feed
L
LINUX DO - 最新话题
博客园_首页
博客园 - Franky
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Privacy & Cybersecurity Law Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
K
Kaspersky official blog
C
Cisco Blogs
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
I
Intezer
罗磊的独立博客
MyScale Blog
MyScale Blog
Last Week in AI
Last Week in AI
A
About on SuperTechFans
G
GRAHAM CLULEY
Y
Y Combinator Blog
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
D
DataBreaches.Net
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
AWS News Blog
AWS News Blog
I
InfoQ
T
The Exploit Database - CXSecurity.com
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 叶小钗
Project Zero
Project Zero

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Stop using SignalStore for everything: hype fades, debt remains.
Romain Geffr · 2026-04-27 · via DEV Community

Let’s be serious for a moment. This probably won’t please everyone, but at some point we need to put the hype aside and focus on facts.

We need to stop treating the NgRx team’s signalStore as the one sacred tool for state management in Angular.

Yes, as we’ll see, signalStore has major strengths. But in practice, and with the way it is often promoted, things can go in the wrong direction.

Why am I criticizing it?

  1. As the stats show, it keeps getting more popular, so more and more developers are using it and discovering it.
  2. There are lots of conference talks promoting it, which is great, but we also need to talk about its limits.
  3. signalStore has left very little room for new signal-based patterns to emerge, especially patterns that leverage the reactive nature of Signals and their deep Angular integration. It has almost become "the right way" to work with Signals, and that is unfortunate.
  4. It is marketed as an all-purpose Swiss Army knife, but what if it is not?

When you use a tool, you need to understand both its strengths and its limits. Otherwise, you risk building technical debt hidden behind the fact that the tool is “cool” or “trendy.”

With that in mind, I will also highlight some Signal-based patterns that are still not very popular, but that signalStore largely misses.

This article is for people who already use signalStore or want to use it, but who do not yet see how things can go wrong (hopefully before it is too late).

Before anything else, I want to congratulate the NgRx team for their work. It is a very powerful tool with major advantages, and it opens the door to a new style of state management in Angular.

I have taken a lot of inspiration from it to build my own state-management tools, and I think that is great for the Angular community.

Where to start? Do you like Angular resources?

Let me tell you a story about a young Angular enthusiast, Simon.

At work, signalStore is used everywhere, and we ended up discussing resources and how to use them.

Personally, I had stopped using signalStore for a while in favor of other patterns. That created some misunderstandings in our discussion, but not in the way you might think.

The goal was to build a page that displays User details based on a URL parameter (id).

My view of the solution:

  • The component/page reads the id as an input (using withComponentInputBindings).
  • We use a resource and pass this id signal as params. Whenever it changes, it automatically triggers an API call for the new id.

Nothing complicated. This is a very standard resource use case.

// component/page
@Component()
class UserDetails {
  id = input.required<string>();

  userService = inject(UserService);

  userDetailsResource = resource({
    params: this.id,
    loader: (id) => this.userService.getUserDetails(id),
  });
}

Enter fullscreen mode Exit fullscreen mode

Simon wanted to avoid handling this logic directly in the component to “separate responsibilities,” and that fit the rest of the project better.

In other words, he wanted to go through signalStore to make the API call.

Have you seen signalStore examples for API calls? 😅

Have you noticed how server state is usually handled in signalStore when following the official NgRx examples?

We will come back to that. For now, the expected behavior is simple: when the id in the URL changes, call the API to fetch new User details.

Here is one possible solution (many variants exist, but the underlying issue is the same):

On the signalStore side, we can imagine an implementation like this. It appears to match the expected behavior.
Id changes => API call fetches new User details.

const userDetailsStore = signalStore(
  withState({ id: undefined as string | undefined }),
  withProps(({ id }, userService = inject(UserService)) => ({
    userDetails: resource({
      params: id,
      loader: ({ params: id }) => userService.getUserDetails(id),
    }),
  })),
);

Enter fullscreen mode Exit fullscreen mode

Then I told him: now pass the id from your component into your signalStore.

And it took me a while to understand why this step was so hard for him.

How do you pass an input to signalStore?

Honestly, how do you pass an input to signalStore? It is a very simple question, but the answer is too complicated for what we need.

If I am not mistaken, one solution looks like this: expose a method that updates id.

const UserDetailsStore = signalStore(
  withState({ id: undefined as string | undefined }),
  withProps(({ id }, userService = inject(UserService)) => ({
    userDetails: resource({
      params: id,
      loader: ({ params: id }) => userService.getUserDetails(id),
    }),
  })),
  withMethods((store) => ({
    setId: (id: string) => patchStore(store, { id }), // method that updates id in signalStore state
  })),
);

Enter fullscreen mode Exit fullscreen mode

On the component side, we need an effect that calls this method whenever id changes.

@Component({ providers: [UserDetailsStore] }) // create a store instance for this component, destroyed with the component
class UserDetails {
  id = input.required<string>();
  userDetailsStore = inject(UserDetailsStore);

  _syncIdWithStore = effect(() => {
    this.userDetailsStore.setId(this.id());
  });
}

Enter fullscreen mode Exit fullscreen mode

That is quite heavy just to extract logic out of a component, right?

PS: Using an effect here is appropriate for this solution.

We have to provide the store, inject it, create an effect to sync component id with store id, and expose a store method to update id.

After that, I told him: wait, I will show you another way to extract logic from a component, and it is much simpler.

Extracting component logic in the Signals era

function userDetailsResource(id: Signal<string>) {
  const userService = inject(UserService);

  return resource({
    params: id,
    loader: ({ params: id }) => userService.getUserDetails(id),
  });
}

Enter fullscreen mode Exit fullscreen mode

@Component()
class UserDetails {
  id = input.required<string>();

  userDetails = userDetailsResource(this.id);
}

Enter fullscreen mode Exit fullscreen mode

And that is it. Yes, this is not a 1:1 port because you cannot inject userDetails into child components. But there was never anything difficult about writing a function that extracts logic from the component.

And we do not see this pattern often, even though Signals made it much simpler once inputs became signals.

If you really want to keep the dependency injection approach to share it with child components, you can also do that using tools I built (craft-ng).

export const { injectUserDetailsQuery, provideUserDetailsQuery } = craftService(
  { name: "UserDetailsQuery", scope: "toProvid" },
  (id: Signal<string>) => {
    const userService = inject(UserService);

    return resource({
      params: id,
      loader: (id) => userService.getUserDetails(id),
    });
  },
);

Enter fullscreen mode Exit fullscreen mode

@Component({ providers: [provideUserDetailsQuery()] })
class UserDetails {
  id = input.required<string>();

  userDetailsQuery = injectUserDetailsQuery(this.id);
}

Enter fullscreen mode Exit fullscreen mode

There is barely any added complexity here.

3 major limitations of signalStore from our example

1. Passing inputs to signalStore is complex and adds cognitive overhead

As we saw, you need a lot of boilerplate to connect an input to signalStore.

That adds mental overhead to understand the data flow and what is happening in the app.

You can create helpers to simplify binding an input to signalStore. I have shared different versions in my posts.

2. signalStore is not well-suited for server state

I was generous and used resources to manage server state (user details) inside signalStore.
But what value did signalStore add here? None, except making API triggering harder.

If we look at the official example that handles server state directly with signalStore:

export const BookSearchStore = signalStore(
  withState(initialState),
  withMethods((store, booksService = inject(BooksService)) => ({
    /* ... */
    // 👇 Defining a method to load all books.
    async loadAll(): Promise<void> {
      patchState(store, { isLoading: true });

      const books = await booksService.getAll();
      patchState(store, { books, isLoading: false });
    },
  })),
);

Enter fullscreen mode Exit fullscreen mode

Without a helper similar to resource, you end up manually handling loading state, errors, and so on, which is heavy and repetitive.

And that is extremely common on the web.

Why does it feel wrong? Because signalStore treats client state (its withState) as the source of truth. It cannot be derived from a resource or from URL-driven state. So it pushes you toward forced synchronization between external sources and its internal state.

Some people like this pattern. I do not. I find the signalStore abstraction neither well-suited nor truly adaptable for this use case.

That is not what an all-purpose Swiss Army knife should be. It does have value, as we will see, but not as a do-everything tool.

If you still doubt it, look at examples in articles. Most are very heavy for a pattern that has already been solved by tools like resources, TanStack Query, and others.

In my tools, I have a query helper that lets me treat the equivalent of a resource as the source of truth, then attach methods/computed values to interact with the fetched state.

I use the same mechanism for query params via queryParam.

From a DX perspective, it works much better.

But what about the next point?

3. Testability and dependency tracking

For testing, signalStore is tested like any Angular service. So it inherits the same strengths and the same weaknesses as classic Angular services.

There is another issue I have not mentioned yet, but it bothers me even though the community accepts it.

In signalStore, injected dependencies are not tracked.

Current Angular services have the same weakness, and signalStore does not compensate for it.

Yet tracking dependencies greatly improves testability: you can ensure required dependencies are provided, or properly mocked.

Like standalone components where you import only what is needed, dependency tracking lets you provide and mock only what is needed.

If you remove a dependency, TypeScript immediately flags tests that still provide/mock a dependency that no longer exists.

That makes tests fail for the right reasons, not because provider/mock configuration drifted.

I also solved this in my tools. If you are interested, check out craftService.

The Swiss Army knife is starting to show some weaknesses.

Should we throw signalStore away? No.

Client state management

After highlighting limits, here is what it does well, even very well compared to most Angular state-management solutions.

It is very well suited to managing one client state.

I said one client state.

One:
So not several, but one single state.

Client:
So not server state, not resource-driven state, not URL-driven state.

Why not server state? Although signalStore can be adapted for server state (API data), it introduces a lot of boilerplate for common cases, and error handling is easy to forget. On top of that, with both Observables and Promises, errors are not typed, which makes business error handling harder. Errors can come from lost connection, forbidden access, and so on. And if you look at most API-call examples, error handling is often terrible (sometimes effectively ignored). I think this happens because NgRx does not provide a real abstraction to help with API/async handling, and leaves that responsibility to users.

Why is it good for client state?

Because you start from a typed initial state.

This means the shape stays consistent, while properties can be updated.

And how are they updated?

Through methods that patch signalStore state.

Or through reactions to events.

And that is great. Inside signalStore itself, you can clearly see how state evolves.

Exposing methods is practical, and it is just as easy to move to event-driven reactions, or combine both.

It can be a good middle ground between method-based and event-based approaches (Redux principle).

Another benefit is exposing derived state directly with computed values.

Everything is in one place, which helps, in my opinion, to drive logic and visualize all the ways state can evolve.

Now, let us talk about another “problem” signalStore solved compared to its predecessor (global store).

Global scope or local scope, but not quite...

One limitation of the global store was that it mainly targeted global state management. And it did that fairly well.

However, when designing an application, you do not only need global state. You also need feature-local state, or even component-local state.

(I may be wrong on some details here, feel free to correct me in the comments.)

Because signalStore can have either global or more local scope, it looks better suited for app architecture.

That is to its credit.

But again, it does not do better than Angular services.

You can still forget to provide it and get a runtime error, even though conceptually this could be prevented.

Another related criticism, and I think the NgRx team could adapt on this, is that signalStore cannot be used inline inside a component the way resources can.

Yet that would let us treat it like an enhanced signal where the component interaction model is fully explicit.

It would also remove issues around input passing and mandatory provider setup.

It would make creating one store per state much simpler, instead of packing several loosely related states together.

In my opinion, that should be the default mode.

Speaking of composition, often presented as a big strength...

signalStore composition: great or not?

In reality, I find it very interesting.

It enables logic reuse very easily.

However (yes, I have things to say here too 😂), being able to compose a store with features that each include state, methods, computed values, and reactions is powerful, but...

This design can be great for sharing logic, but it can also distort the purpose of a store.

You end up seeing signalStores used as facades.

Where signalStore adds no real value and actually makes things harder.

As in this example, the facade is handled by a classic service, which could also be a function (credit: @lucas Garcia on LinkedIn).

@Service()
class MyFacade() {
    public readonly state1 = inject(State1); // State1 created via signalStore
    public readonly state2 = inject(State2); // State2 created via signalStore

    public readonly derivedState = computed(() => {
        // derivation logic based on state1 and state2
    });

}

Enter fullscreen mode Exit fullscreen mode

Where is the complexity here? At what point does signalStore really add value when composing several different states?

Almost none.

My rule: signalStore manages one state, not several.

And I do not use it as a facade, or anything close to that.

Personally, to avoid these drifts, I prefer composing a state only through logic composition and derived state (no extra base state added through composition). See my state primitive.

That keeps a clear responsibility for what our state-management “container” should do.

And I apply the same principle to my other primitives query, mutation, and queryParam, which, similar to Signal Form, derive behavior from source state.

Also, if you think the event system compensates for the weak points above, I have one more criticism.

signalStore events are not as declarative as they should be

I struggle with NgRx events in general, and while signalStore likely improves over its predecessor, it still has this major flaw.

You cannot derive an event with official utilities. Instead, you must give it a description of what it represents.

That event is either dispatched after a user action, or from an effect that computes and then emits an event.

And those effect-driven events are what bother me.

Instead of going through a separate description, we could directly write the event-triggering code where the event is declared.

That would make events much more declarative and avoid effects that need to be provided just to be active.

And if an event is unused, it could be tree-shaken by the compiler, unlike a classic event setup.

After saying all this, I still want to be clear: event-based architecture remains one of the most interesting approaches for state and business logic.

And skipping event-based patterns entirely just because events are not declarative would be a mistake.

About typing

I will not go too deep, but even though signalStore’s underlying pattern is very interesting and likely ahead of its time, it also has limits.

I worked with it a lot. I reimplemented the whole accumulation mechanism because I found it fascinating.

I then built server-state management tools specifically for signalStore, and I can tell you: it is very, very complicated.

Personally, I think there is one typing layer too many, which makes custom patterns extremely hard to build.

And when you want to work with generic features based on the main withX pattern, from what I remember, you cannot do it without two utility helpers.

I proposed a simplification for withFeature, but it was rejected because it did not handle generic-parameter features better than their current mechanism.

Link to one of my articles about building custom signalStore features with good DX.

I wanted to mention this because it is part of signalStore’s limitations.

But it is not the core issue.

Conclusion

signalStore introduced a very interesting composition pattern.

It is excellent for managing one client state and provides good DX, which is already valuable.

But as we saw, it is not a Swiss Army knife.

It does not replace specialized tools for URL-related state or server state (and current resources are not flawless either).

(craft-ng handles all that 🤫)

If you keep using signalStore, my advice is: use it to manage one client state, no more.

Avoid injecting dependencies into it as much as possible (it is still fine when interacting with the browser, for example API calls, local storage, etc.).

And do not use it as a facade or as an orchestrator that composes several states.

That way, you keep its best strengths and can combine it with other patterns that are often more appropriate.


I am Romain Geffrault.
Angular developer and creator of @craft-ng
Follow me for more Angular content

Docs: https://ng-angular-stack.github.io/craft/