Announcing NgRx Traits 22 The NgRx team released NgRx Traits version 22, compatible with Angular 22 and NgRx Signals 22, headlined by a new `withLink` feature that connects a store to component signals, models, inputs, outputs and Angular Signal Forms without hand-written sync logic. `withLink` generates a `link[StateProp]()` method returning a `linkedSignal` that reads from and writes to the store, with options including `set` to route writes through a store method, `storeEditsWhen` to buffer edits until validation passes, and `syncWith` with `readMap`/`writeMap` for two-way model synchronization. The release also adds an Angular Resource view of calls, new state setter features, shorter `withEntities*` calls, a refreshed docs site, and an AI agent skill that teaches coding agents to use the library. Hey everyone We're excited to announce NgRx Traits version 22, fully compatible with Angular 22 and NgRx Signals 22 Along with the version bump, this release brings the biggest new feature the library has had in a while: withLink , a way to connect your store to component signals, models, inputs, outputs and Angular Signal Forms, without writing sync logic by hand. There is also an Angular Resource view of your calls, new state setter features, a shorter way to call the withEntities features, a refreshed docs site, and an AI agent skill that teaches coding agents how to use the library. When a store has to work with a form, or with a parent component's model , input or output , you usually end up writing glue code: a linkedSignal to follow the store, sometimes effects to copy changes back and forth, and guards so the two sides don't keep triggering each other in an echo loop. withLink generates that for you. Given a state prop, withLink generates a link StateProp method that returns a linkedSignal : reading it reads the store prop, and setting it writes to the store. js const Store = signalStore withState { filter: { search: '' } } , // 👇 generates linkFilter withLink 'filter' , ; // In component store = inject Store ; filter = this.store.linkFilter ; // filter = { search: '' } filter.set { search: 'shoes' } ; // store.filter = { search: 'shoes' } By default writes go through patchState , but you can route them through a store method instead with set : js const ProductsStore = signalStore withEntities productEntityConfig , withEntitiesLocalFilter productEntityConfig, { defaultFilter: { search: '' }, filterFn: entity, filter = filter?.search || entity.name.toLowerCase .includes filter.search.toLowerCase , } , // 👇 generates linkProductEntitiesFilter withLink 'productEntitiesFilter', { set: value, store = store.filterProductEntities { filter: value, debounce: 0 } , } , ; Because the returned value is just a WritableSignal , it can be handed straight to Signal Forms: export class ProductListComponent { store = inject ProductsStore ; // typing in the form filters the entities, // resetting the filter in the store updates the form filterForm = form this.store.linkProductEntitiesFilter , value = { required value.search ; } ; } That form writes to the store on every keystroke, valid or not. If you only want validated data to land in the store, pass storeEditsWhen : the returned signal becomes a buffer, and edits are held back until the gate opens, then pushed automatically. export class ProductListComponent { store = inject ProductsStore ; // buffers the form value, only valid data reaches the store. // the : boolean annotation is needed because filterForm is declared below, // without it typescript reports a circular inference formData = this.store.linkProductEntitiesFilter { storeEditsWhen: : boolean = this.filterForm .valid , } ; filterForm = form this.formData, value = { required value.search ; } ; } If you need to keep the store in sync with a component's model , pass it as syncWith : export class ProductSearchComponent { store = inject ProductsStore ; // when the parent writes the model, the store updates; // when the store changes, the model and the parent updates filter = model<{ search: string } { search: '' } ; linked = this.store.linkProductEntitiesFilter { syncWith: this.filter } ; filterForm = form this.linked ; } If the model's type doesn't match the store's, readMap and writeMap convert between the two: export class ProductSearchComponent { store = inject ProductsStore ; // the parent works with a plain string, the store filter is { search: string } search = model