Skip to content

Build1 publisherNot yet confirmed elsewhere2 min readPublished

NgRx Traits 22 generates the store-to-form sync code Angular teams wrote by hand

NgRx Traits 22 adds withLink for Angular 22, generating the two-way sync between signal stores and component models, inputs and Signal Forms. Teams can delete hand-written sync effects and echo-loop guards and inherit the library's write timing and equality rules.

The Engineer · Build desk

How we use AISend a correction

What happened

  • For each state prop, withLink adds a link method, such as linkFilter(), that returns a linkedSignal reading from the store and writing back into it.
  • The returned value is a plain WritableSignal, so it can be passed directly to Angular's form() to build a Signal Form over store state.
  • A syncWith option keeps the store and a component's model() in step both ways, so a parent's writes reach the store and store changes reach the parent.
  • The release also adds an Angular Resource view of calls, new state setters, shorter withEntities* calls, a refreshed docs site and an AI agent skill for coding agents.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision Teams now set a write policy per form. Left at the default, half-typed invalid values reach store state that other components read, so validated forms need storeEditsWhen added on purpose.
  • constraint Removing hand-written echo guards puts loop safety on withLink's equal check, so object-valued props are only as safe as the comparator it uses.
  • capability Components built around an input() and output() pair can bind to a store without a rewrite to model(), and one-way sync needs only readFrom or writeTo.

The code withLink replaces has three parts. According to the release announcement, a store that has to work with a form, or with a parent component's model(), input() or output(), usually ends up with a linkedSignal that tracks the store, in some cases effects that push changes between the two sides, and guards that stop each side's updates from setting off the other in an echo loop [4]. withLink generates that code for a named state prop [4].

Writes go through patchState by default [6]. A `set` option sends them through a store method instead [6], and I'd use it for any prop with derived state behind it. In the announcement's products example, `set` calls `store.filterProductEntities({ filter: value, debounce: 0 })` [7]. The filter function and its debounce setting stay in the store [7].

Both sync directions are guarded by `equal`: a write equal to what the store already holds is skipped [14]. The available announcement text breaks off partway through the rule for the other direction, before naming a default comparator [17]. Before deleting a hand-written deep-equality guard, I'd want that default in writing. Every example in the release holds the filter as an object, starting from `{ search: '' }` [15]. The readMap example builds a fresh object on each read with `(search) => ({ search })` [12]. Under a reference-identity check, two of those objects holding the same string compare as different [16].

The buffered form has a constraint that comes from class field order. In the storeEditsWhen example, the gate reads `this.filterForm().valid()`, but `filterForm` is declared below the buffered signal it wraps [10]. Without a `: boolean` return annotation on the gate, TypeScript reports a circular inference [10]. The form needs the buffer to exist first, and the buffer needs the form to decide when to flush [10]. The announcement explains the annotation in a code comment beside the example, where a developer chasing that compiler error will look first [10].

What to watch

  • The full withLink documentation for equal, including its default comparator and the rule for store-to-component updates on object-valued props.
  • Bug reports from teams replacing effect-based sync with syncWith on object state, especially round trips through readMap.
  • Whether a later NgRx Traits or TypeScript release removes the explicit : boolean annotation on storeEditsWhen gates.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories