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

推荐订阅源

aimingoo的专栏
aimingoo的专栏
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
C
Cybersecurity and Infrastructure Security Agency CISA
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
爱范儿
爱范儿
Spread Privacy
Spread Privacy
P
Palo Alto Networks Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
T
The Blog of Author Tim Ferriss
The Cloudflare Blog
WordPress大学
WordPress大学
小众软件
小众软件
H
Help Net Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Blog — PlanetScale
Blog — PlanetScale
V2EX - 技术
V2EX - 技术
H
Hacker News: Front Page
Last Week in AI
Last Week in AI
D
DataBreaches.Net
V
V2EX
S
Securelist
C
CXSECURITY Database RSS Feed - CXSecurity.com
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
Cloudbric
Cloudbric
月光博客
月光博客
Y
Y Combinator Blog
博客园 - 三生石上(FineUI控件)
N
News and Events Feed by Topic
K
Kaspersky official blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
Secure Thoughts
S
Security Affairs
W
WeLiveSecurity
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The Last Watchdog
The Last Watchdog
AI
AI
有赞技术团队
有赞技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
罗磊的独立博客
Google DeepMind News
Google DeepMind News
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
N
Netflix TechBlog - Medium
PCI Perspectives
PCI Perspectives
SecWiki News
SecWiki News
IT之家
IT之家
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog

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
Arrête d’utiliser SignalStore pour tout : la hype passe, la dette reste
Romain Geffr · 2026-04-27 · via DEV Community

On va parler sérieusement, ça ne va sans doute pas plaire à beaucoup de monde, mais à un moment il faut mettre un peu de côté la hype et se concentrer sur les faits.

Il faut arrêter de voir le signalStore de la team NgRx comme le sacro-saint outil de la gestion d'état dans Angular.

Alors oui, comme on va le voir, le signalStore a de gros avantages, mais en pratique et de la façon dont il est mis en avant, ça va dans le mauvais sens.

Pourquoi je le critique ?

  1. Comme on peut le voir d'après les stats, il est de plus en plus populaire, donc de plus en plus de devs l'utilisent et le découvrent
  2. Il y a énormément de conférences pour le promouvoir, et c'est très bien, mais il faut aussi parler de ses limites
  3. Le signalStore n'a pas laissé beaucoup de place à l'émergence de nouveaux patterns liés aux signals avec leur aspect réactif et leur forte intégration dans Angular. Et il s'est presque imposé comme la bonne façon de travailler avec les signaux, ce qui est dommage.
  4. Mis en avant pour son côté qui s'adapte à tout tel un bon couteau suisse, mais si ce n'était pas le cas ?

Quand on utilise un outil, il faut être conscient de ses avantages et de ses limites (sinon le risque est de se retrouver avec une dette technique dissimulée par le fait que l'outil est "cool" ou "à la mode").

Dans ce sens, je vais aussi mettre en avant des patterns basés sur les Signals très peu populaires, mais dont le signalStore passe à côté.

Cet article s'adresse à tous ceux qui l'utilisent ou souhaitent l'utiliser, et qui ne voient pas encore comment ça peut mal tourner (en espérant que ce ne soit pas trop tard).

Mais avant toute chose, je tiens à féliciter l'équipe de NgRx pour le travail qu'ils ont fait, c'est un outil très puissant qui a d'énormes avantages, et qui ouvre la voie à un nouveau style de gestion d'état dans Angular.

Je m'en suis énormément inspiré pour faire mes propres outils de gestion d'état, et je pense que c'est une très bonne chose pour la communauté Angular.

Par où commencer ? Vous aimez les ressources d'Angular ?

Eh bien, je vais vous raconter une anecdote avec un jeune passionné d'Angular, Simon.

À son travail, le signal store est implémenté partout, et on en vient à parler des ressources et de comment les utiliser.

Personnellement, ça faisait un moment que je n'utilisais plus le signal store au profit d'autres patterns, ce qui a engendré des incompréhensions dans notre discussion, mais sans doute pas comme vous l'imaginez.

L'objectif était d'avoir une page où on affiche les détails d'un User en fonction d'un paramètre (id) venant de l'URL.

Ma vision de la solution :

  • l'id est récupéré par le component/page via un input (et l'utilisation de withComponentInputBindings)
  • On utilise une resource en lui passant en params cet id sous forme de signal (dès qu'il change, cela va déclencher un appel API associé à ce nouvel id)

Rien de compliqué, c'est une utilisation très classique des ressources.

// 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 voulait éviter de gérer la logique dans le composant directement pour "découper les responsabilités" et ça collait mieux au reste du projet.

Traduction, on va passer par un signalStore pour faire l'appel API.

Vous avez déjà vu les exemples du signalStore pour faire des appels API ? 😅

Vous avez déjà remarqué la façon dont sont gérés les server-state dans le signalStore en suivant les exemples officiels de NgRx ?

On y reviendra, pour l'instant, l'idée reste que le comportement attendu est : l'id dans mon URL change / il faut que j'appelle l'API pour récupérer les nouveaux détails du User.

Voici une potentielle solution (plein de variantes existent mais le problème reste le même) :

Côté signalStore, on peut imaginer une implémentation comme celle-ci. Elle semble correspondre à ce qui est attendu.
L'id change => appel API pour récupérer les nouveaux détails du User.

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

Puis je lui dis : maintenant, tu passes l'id récupéré par ton composant à ton signalStore.

Et là, j'ai mis du temps à comprendre pourquoi il galérait autant à faire cette étape.

Comment passer un input à un signalStore ?

En vrai, comment on fait pour passer un input à un signalStore ? C'est une question très simple, mais la réponse est trop compliquée pour ce qui est attendu.

Si je ne dis pas de bêtises, une solution pour le faire ressemble à ça, où on expose une méthode pour mettre à jour l'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 }), // méthode pour mettre à jour l'id dans le state du signalStore
  })),
);

Enter fullscreen mode Exit fullscreen mode

Côté composant, on va devoir faire un effect pour appeler cette méthode à chaque fois que l'id change.

@Component({ providers: [UserDetailsStore] }) // créer une instance du store pour ce composant, qui se détruira à la destruction du composant
class UserDetails {
  id = input.required<string>();
  userDetailsStore = inject(UserDetailsStore);

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

Enter fullscreen mode Exit fullscreen mode

C'est quand même lourd comme implémentation pour extraire la logique de notre composant, vous ne trouvez pas ?

Ps: C'est une utilisation d'un effect approprié pour cette solution

On doit provider le store, l'injecter, faire un effect pour synchroniser l'id du composant avec le store, et faire en sorte d'exposer une méthode du store pour faire la mise à jour de l'id.

Suite à ça, je lui ai dit : attends, je vais te montrer une alternative pour extraire la logique du composant et ça va être follement simple.

Extraire la logique d'un composant à l'ère des Signals

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

Et voilà. Alors oui, ce n'est pas un portage 1 pour 1, car on ne peut pas injecter userDetails dans des composants enfants. Mais à aucun moment ça n'a été difficile d'écrire une fonction qui extrait la logique de notre composant.

Et ça, on ne le voit que très peu, car c'est un pattern qui est très simplifié depuis le passage des inputs en signal.

Si tu tiens vraiment à passer par le système d'injection pour le récupérer dans des composants enfants, tu peux aussi faire ça grâce aux outils que j'ai mis en place (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

Voilà, il n'y a presque pas de complexité ajoutée.

3 grosses limites du signalStore venant de notre exemple

1 - Le passage d'inputs à un signalStore est compliqué / ajout de charge mentale

Comme on l'a vu, il faut ajouter beaucoup de boilerplate pour faire le lien entre un input et un signalStore.

Cela ajoute une charge mentale pour comprendre le flow de données, et ce qui se passe dans notre app.

Il est possible de créer un utilitaire pour faciliter la liaison entre un input et un signalStore, j'en ai partagé différentes versions dans mes posts.

2 - Le signalStore n'est pas adapté pour gérer les server states

J'ai été gentil, j'ai utilisé les resources pour gérer le server state (user details) dans le signal store.
Mais quel a été l'intérêt du signal store, sa plus-value ? Rien si ce n'est que ça nous a compliqué la vie pour déclencher les appels API.

Si on reprend l'exemple officiel, qui met directement la gestion d'un server state avec l'utilisation du 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

Sans un utilitaire similaire à une resource, on se retrouve à devoir gérer manuellement l'état de chargement, les erreurs, etc., ce qui est lourd et fastidieux.

Et qui dans le web est très courant...

Et pourquoi ça fonctionne "mal", c'est que le signalStore considère l'état client (son withState) comme source de vérité, il ne peut pas être dérivé à partir d'une resource, ou d'un state venant de l'URL. Ce qui le contraint à devoir faire des synchronisations forcées entre des sources et son state interne.

Certains apprécient ce pattern, moi je trouve que l'abstraction du signalStore n'est ni appropriée ni adaptable.

Ce n'est pas digne d'un couteau suisse, ça a un intérêt comme on va le voir après, mais pas pour un outil à tout faire.

Si t'as encore un doute, regarde un peu les exemples que tu peux trouver sur les différents articles, tu verras que c'est très lourd pour un pattern qui a été résolu depuis un moment grâce à des outils comme les ressources, TanStack Query, etc.

Dans mes outils, j'ai un utilitaire query qui me permet de considérer l'équivalent d'une resource comme source de vérité, et d'y associer des méthodes/computed pour interagir avec l'état récupéré.

J'ai le même mécanisme pour les query params avec queryParam.

Et ça marche bien mieux d'un point de vue DX.

Mais qu'en est-il du point suivant ?

3 - Testabilité & tracking des dépendances

Côté testabilité, le signalStore se teste comme n'importe quel autre service. Il a donc les mêmes avantages et les mêmes inconvénients que les services d'Angular classiques.

Un autre problème que je n'ai pas signalé, mais qui me pose problème bien que ce soit accepté par la communauté.

C'est le fait que l'on ne tracke pas les dépendances (services injectés) dans le signalStore.

Les services d'Angular actuels présentent cette même faiblesse, mais on voit que le signalStore ne la compense pas.

Pourtant, tracker les dépendances simplifie grandement la testabilité (s'assurer que les dépendances sont bien provider, ou mocker).

À l'instar des composants standalone où on importe juste ce qu'il faut pour le composant, le tracking des dépendances permet de provider et mocker uniquement ce qu'il faut.

On retire une dépendance ? Une erreur TS apparaît dans les tests, car on essaye de provider ou mocker une dépendance qui n'existe plus.

Ça assure que le test rate pour les bonnes raisons et pas à cause de problème de configuration provider/mock.

J'ai aussi résolu ce problème avec mes outils, je te laisse aller voir si ça t'intéresse via craftService

Le couteau suisse commence à présenter quelques faiblesses...

Faut-il jeter le signalStore à la poubelle ? Non

Gestion des client states

Après avoir exposé quelques limites, voici ce sur quoi il est bon, même très bon par rapport à la plupart des solutions de gestion d'état dans Angular.

Il est très adapté pour gérer 1 état client.

J'ai bien dit : 1 état client.

1 :
Donc pas plusieurs, mais 1 seul.

Client:
Donc pas un server state, pas un état venant d'une resource, pas un état venant de l'url.

Pourquoi il est bon à faire ça ?

L'intérêt c'est qu'on part d'un état initial typé.

Cela veut dire qu'il aura toujours la même forme, mais que ces propriétés pourront être mises à jour.

Et comment vont-elles être mises à jour ?

Par des méthodes qui vont faire des patchs sur le state du signalStore.

Ou encore via des réactions à des events.

Et c'est trop bien, dans le signalStore lui-même on peut voir exactement comment va évoluer notre state.

L'exposition de méthodes est pratique et il est tout aussi simple de passer sur des réactions à des events ou un mix des deux.

Ça peut être utilisé comme un bon compromis entre une approche method-based et event-based (principe lié à Redux).

L'autre intérêt est qu'on peut aussi exposer des états dérivés directement liés via les computed.

On a tout en même endroit, ce qui aide, je trouve, à piloter la logique et à visualiser les différentes évolutions de notre état.

Maintenant, parlons d'un autre "problème" qu'a résolu le signalStore, par rapport à son prédécesseur (global store) ?

Scope global ou local mais pas trop...

Une limitation du global store, c'est qu'il était surtout fait pour gérer des états globaux. Et il le faisait plutôt bien.

Toutefois, quand on architecture une application, on n'a pas que besoin d'états globaux, mais aussi d'états locaux à une feature, ou même à un composant.

(J'avoue ne pas être sûr de ne pas dire de bêtises sur ce point, n'hésitez pas à me corriger dans les commentaires).

Le signalStore pouvant avoir une portée globale ou plus locale, il semble plus adapté pour architecturer une application.

Et c'est tout à son honneur.

Mais encore une fois, il ne fait pas mieux que les services d'Angular.

On peut oublier de le provider et avoir une erreur au runtime, qui conceptuellement pourrait être évitée.

Un autre reproche lié à ce sujet, mais dont je pense que l'équipe NgRx peut s'adapter, c'est qu'il n'est pas possible d'utiliser le signalStore en inline dans un composant comme on peut le faire avec les ressources.

Pourtant, ça permettrait d'avoir une sorte de signal amélioré où tout est déclaré explicitement sur la façon dont le composant interagit avec.

Cela lèverait aussi les problèmes du passage d'inputs, d'obligation de devoir le provider...

Ça simplifierait grandement la création d'un signalStore par état, plutôt qu'il soit composé de plusieurs pas forcément liés.

Ça devrait, selon moi, être son mode par défaut.

Et si on en parlait de la composition souvent mise en avant ?

La composition du signalStore top ou flop ?

Eh bien, en réalité, je la trouve très intéressante.

Et elle permet la réutilisation de logique de manière très simple.

Toutefois (oui, j'ai des trucs à dire aussi à ce sujet 😂), le choix de pouvoir composer un store avec des features qui, elles, contiennent des states, méthodes, computed et réactions, c'est très bien, mais...

C'est un choix qui peut être très intéressant pour faire du partage de logique, mais qui peut aussi amener à dénaturer l'intérêt du store.

On en arrive à voir des signalStore qui vont servir de façade.

Où le signalStore n'a juste aucune plus-value et rend même les choses plus compliquées.

Comme on peut le voir dans cet exemple, la façade est gérée par un service classique, qui pourrait aussi être une fonction (cf. @lucas Garcia sur Linkedin).

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

    public readonly derivedState = computed(() => {
        // logique de dérivation à partir de state1 et state2
    });

}

Enter fullscreen mode Exit fullscreen mode

Elle est où la complexité ? À quel moment le signalStore a vraiment un intérêt à composer plusieurs states différents ?

Presque pas d'intérêt.

Ma règle : le signalStore gère un state, pas plusieurs.

Et on ne s'en sert pas pour faire une façade, ou tout ce qui peut s'en approcher.

Et personnellement, pour éviter d'en arriver à ces dérives, je préfère composer un state uniquement via de la composition de logique et d'états dérivés (pas de state ajouté via la composition). Cf. ma primitive state.

Ça permet de garder une responsabilité claire de ce que doit faire notre "conteneur" de gestion d'état.

Et ce principe, je l'applique aussi à mes autres primitives query, mutation, queryParam qui, à l'instar du Signal Form, dérivent la logique à partir de l'état de la source.

D'ailleurs, si tu penses que le système d'events compense les autres points faibles, j'ai aussi un point à critiquer.

Les events du signalStore ne sont pas si déclaratifs que ça...

J'ai clairement du mal avec les events de NgRx, et l'approche avec le signalStore, bien qu'il fasse sans doute mieux que son papa, a toujours ce gros défaut.

On ne peut pas dériver un event (avec des utilitaires officiels). Non, on doit lui donner une description pour dire ce que ça représente.

Cet event est soit dispatché suite à une action d'un utilisateur, soit depuis un effect qui fait des calculs puis émet un event.

Et ce sont ces derniers évents qui me posent problème.

Plutôt que de devoir passer par une description, on pourrait directement écrire le code qui déclenche l'event au moment de sa déclaration.

Ça permet d'avoir un event beaucoup plus déclaratif, et d'éviter un effect qui doit être provider pour être actif.

Et en plus, si ce n'est pas utilisé, c'est tree-shaké par le compilateur, ce qui n'est pas le cas d'un event classique.

Après avoir dit tout ça, je tiens à préciser que l'approche event-based est toujours une des approches les plus intéressantes pour gérer un état et la logique métier.

Et qu'il est préjudiciable de ne pas faire d'event-based dans un projet parce que les events ne sont pas déclaratifs.

Côté typage

Je ne vais pas trop entrer dans les détails, mais même si le pattern utilisé par le signal store est très intéressant, et sans doute précurseur, il a aussi des limites.

J'ai énormément travaillé avec. J'avais recréé tout ce mécanisme d'accumulation, car je le trouvais fascinant.

J'ai ensuite créé des outils de gestion de server state dédiés au signal store, et je peux vous dire que c'est très, très compliqué.

Personnellement, je trouve qu'il y a une couche en trop dans le typage, ce qui rend extrêmement difficile la création de patterns custom.

Et quand on souhaite travailler avec des features génériques basées sur le pattern principal withX, de mémoire, ce n'est pas possible sans utiliser deux utilitaires.

J'avais fait une proposition pour simplifier withFeature, mais elle a été rejetée, car elle ne gère pas mieux que leur mécanisme actuel les features avec un paramètre générique.

Lien vers un de mes articles, pour créer des features personnalisées avec une bonne DX avec le signalStore.

Je tenais à mentionner ce point, car il fait partie des limites de ce que permet de faire le signalStore.

Mais ce n'est pas vraiment lui qui pose vraiment problème.

Conclusion

Le signalStore a permis la découverte d'un pattern très intéressant autour de la composition.

Il est roi pour gérer un état client où il offre une bonne DX, et c'est déjà pas mal.

Mais comme on l'a vu, ce n'est pas un couteau suisse.

Il ne remplace pas un outil spécialisé pour gérer les états liés à l'URL ou encore pour gérer les server states (les resources actuelles ne sont pas sans défaut non plus).

(craft-ng fait tout ça 🤫)

Si tu devais continuer à l'utiliser, je te conseillerais de toujours l'utiliser pour gérer un état client, pas plus.

D'éviter le plus possible de faire des injections dedans (ça reste ok, si c'est pour intéragir avec le navigateur, ex: appel api, stockage local, etc.).

Et de ne pas l'utiliser pour faire des façades ou des compositions/orchestrateurs de plusieurs states.

De cette façon, tu garderas toujours les bons côtés de celui-ci et tu pourras profiter d'autres patterns souvent plus appropriés.


Je suis Romain Geffrault.
Développeur Angular et créateur de @craft-ng
Suis-moi pour plus de contenu sur Angular

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