Tous les articles et vidéos

L'état en frontend d'entreprise : le propriétaire d'abord

L'état dans une app ou React d'entreprise : nommer son type, lui donner un propriétaire, dériver le reste. , , URL, formulaires.

Adil Ben Moussa
08 novembre 2026· 5 min
L'état en frontend d'entreprise : le propriétaire d'abord

Un conseiller du service client ouvre le lien envoyé par un client : une page de compte, filtrée sur une catégorie. La page affiche toutes les catégories, parce que le filtre vivait dans un champ du composant et non dans l'URL. Personne n'a écrit de bug. L'état avait simplement été rangé sous le mauvais type.

Voilà le problème de l'état dans les applications d'entreprise. Le choix de la bibliothèque vient en dernier. D'abord, vous décidez de quel type est chaque morceau d'état, qui le possède, et combien de temps il vit.

Cinq types d'état

  • Les données serveur. Comptes, opérations. Le backend en est propriétaire ; le frontend en garde une copie qui peut être périmée (partie 3).
  • L'état d'UI. Quel panneau est ouvert. Il appartient à un composant et disparaît avec lui.
  • L'état de formulaire. Un brouillon, sa validation, le fait qu'il ait été modifié ou non.
  • L'état d'URL. Les filtres, les onglets, l'enregistrement sélectionné. Tout ce qu'un lien, un de support ou le bouton retour doit restaurer.
  • L'état de session. L'utilisateur et ses rôles. Vraiment global.

Un bug d'état fréquent, c'est un type rangé comme un autre : des données serveur copiées dans un composant, un filtre en mémoire qui devrait être dans l'URL, des rôles mis en cache dans un que personne ne rafraîchit.

L'URL est un état

Si quelqu'un peut vouloir la partager, la mettre en favori ou y revenir, elle a sa place dans l'URL. Dans Angular, withComponentInputBinding relie les paramètres de route aux inputs du composant et, par défaut, le routeur remet un input à undefined quand la valeur manque dans la route, ce qui « garantit que des données périmées ne sont pas conservées ». Les query parameters se relient de la même façon, un filtre peut donc y vivre aussi.

Un store par domaine, un store global au maximum

Je donnerais à chaque domaine son propre store : comptes, cartes, demandes. Une équipe possède son store comme elle possède sa bibliothèque. Un store global unique paraît bien rangé le premier jour, puis devient l'endroit où l'état de chaque équipe croise les bugs de toutes les autres.

Ce qui reste global, c'est ce qui est transversal par nature : la session, les permissions, peut-être les . Tout le reste demeure près de la fonctionnalité qui en est propriétaire.

Dériver, ne pas dupliquer

Un solde total n'est pas un état. C'est une fonction des comptes. Angular le dit clairement : « Évitez d'utiliser des effects pour propager des changements d'état », utilisez plutôt des . React dit la même chose à sa manière : « Quand une valeur peut être calculée à partir des props ou de l'état existants, ne la mettez pas dans l'état. Calculez-la plutôt pendant le rendu. »

Chaque copie d'une valeur est un deuxième propriétaire. Deux propriétaires finiront par se contredire.

Les longs formulaires réglementés

Une demande de crédit ou un formulaire d'admission de patient, c'est de l'état avec des règles : des brouillons qui survivent à la pause déjeuner, une validation qui reprend celle du serveur, et un suivi des modifications pour que personne ne perde une heure de saisie. Le serveur valide toujours ; la vérification côté client sert à la personne qui tape.

Pour du code neuf, les d'Angular synchronisent automatiquement le modèle du formulaire avec ses champs. L'équipe Angular reste prudente : ils « fonctionnent le mieux dans les nouvelles applications construites avec les signals », et « les reactive forms restent un choix solide » dans une application existante qui les utilise. Dans une base de code de dix ans, c'est le bon conseil. Ne migrez pas vos formulaires par effet de mode.

Signals ou RxJS

Ma règle : les signals pour l'état (une valeur, maintenant), RxJS pour les événements dans le temps (frappes au clavier, , relances). Le pont existe : toSignal transforme un Observable en signal et « se comporte comme le pipe async dans les templates ». Les signals s'accordent aussi avec Angular , où « la mise à jour d'un signal lu dans un template » fait partie des notifications qui déclenchent la détection de changements.

Sur Atlas Bank

AccountsStore, dans libs/data-access/banking, garde son état privé et n'expose que des signals en lecture seule et des  :

  private readonly accountsState = signal<readonly Account[]>([]);
  private readonly transactionsState = signal<readonly Transaction[]>([]);
  private readonly beneficiariesState = signal<readonly Beneficiary[]>([]);
  private readonly loadingState = signal(false);
  private readonly loadedState = signal(false);
  private inFlight?: Promise<void>;
 
  readonly accounts = this.accountsState.asReadonly();
  readonly transactions = this.transactionsState.asReadonly();
  readonly beneficiaries = this.beneficiariesState.asReadonly();
  readonly loading = this.loadingState.asReadonly();
  readonly loaded = this.loadedState.asReadonly();
 
  readonly totalBalanceMad = computed(() =>
    this.accountsState()
      .filter((a) => a.status !== 'closed')
      .reduce((sum, a) => sum + a.balanceMad, 0),
  );

asReadonly() expose une valeur « sans permettre de la modifier directement », le store en reste donc le seul propriétaire. La page de détail d'un compte reçoit son id de la route grâce à withComponentInputBinding et dérive la liste filtrée avec computed. Son filtre par catégorie, en revanche, est un signal du composant. Acceptable pour une démo ; dans le parcours du service client d'une banque, je le passerais en query parameter.

L'écran de virement montre l'ancienne forme : des champs simples, des abonnements gérés à la main, et un markForCheck() derrière. L'AGENTS.md du dépôt y voit « un signe qu'il faut migrer, pas un pattern à copier ».

Ce que j'ai laissé de côté, et pourquoi

  • Les comparaisons , , , . Un tour des bibliothèques. La propriété décide d'abord ; n'importe laquelle peut ensuite porter le résultat.
  • L' comme concept. Elle découle des signals en lecture seule et des valeurs dérivées.
  • L' côté client. Rare dans un frontend, c'est une forme de backend.
  • L'état atomique. Le vocabulaire de certaines bibliothèques React pour la même idée que « dériver, ne pas dupliquer ».

Dès lundi

  • Prenez un écran et étiquetez chaque morceau d'état avec son type : serveur, UI, formulaire, URL, session.
  • Trouvez une valeur stockée deux fois. Faites de l'une d'elles un computed (ou un calcul pendant le rendu).
  • Trouvez un filtre qu'un ticket de support ne permet pas de reproduire. Passez-le dans l'URL.

Ensuite : la partie 3, sur le réseau et le cache.

Pour votre agent de code

Les règles de cet article, écrites pour un agent. Collez-les dans votre AGENTS.md, enregistrez-les comme , ou indiquez le fichier à votre agent.

Pour votre agent de code Télécharger le .md
# State rules for enterprise apps

Source: https://tt-tech.io/blog/enterprise-frontend-state-ownership
For Angular and React frontends. Add to AGENTS.md or save as a skill. Your project's own rules win where they differ.

## Rules

1. Before you add state, name its kind: server data, UI state, form state, URL state or session state. Store it as that kind.
2. Put anything a link, a bookmark, a support ticket or the back button must restore in the URL: filters, tabs, the selected record. In Angular, bind route and query parameters to inputs with `withComponentInputBinding`, and expect a missing parameter to arrive as `undefined`.
3. Server data belongs to the backend. Never copy it into a component. Read it from the store of its domain.
4. Give each domain its own store (accounts, cards, requests), owned by the team that owns the domain. Never add a single global store for everything.
5. Keep global only what is cross-cutting by nature: the session, the permissions, maybe feature flags. Do not cache roles in a store nobody refreshes.
6. Keep UI state (which panel is open) in the component that owns it.
7. Derive, do not duplicate. If a value can be calculated from state, use `computed` in Angular or calculate it during render in React. Never store it as a second copy.
8. Never use `effect` to propagate state changes. Use `computed` instead.
9. Keep a store's writable state private. Expose read-only signals with `asReadonly()` and derived values, so the store is the only owner.
10. In long regulated forms, keep drafts, mirror the server's validation and track dirty state. The server still validates; the client check is for the person typing.
11. Use Signal Forms in new code built with signals. Keep reactive forms in an existing app that uses them. Do not migrate forms for fashion.
12. Use signals for state (a value now) and RxJS for events over time (keystrokes, websockets, retries). Bridge them with `toSignal`.

## Before you say done

- For every new piece of state in the diff, list its kind and its owner in the pull request.
- Search the diff for `effect(` calls that set other state, and for stored values that could be a `computed` or a calculation during render.
- For every new filter, tab or selection, confirm it lives in the URL, or say why not.

Sources

  1. Angular, Other routing tasks, documentation
  2. Angular, Side effects for non-reactives APIs, documentation
  3. React, You Might Not Need an Effect, documentation
  4. Angular, Forms with Angular Signals, documentation
  5. Angular, RxJS interop with Angular signals, documentation
  6. Angular, Angular without ZoneJS (Zoneless), documentation
  7. Angular, Angular Signals, documentation
Partager cet article