This series explores how we can maintain a declarative coding approach while adapting our features to increasing levels of complexity.
Level 4: Reusable State Patterns
Notice what happens when we isolate the object that manages our state:
export class ColorsComponent {
adapter = createAdapter<string[]>({ // Added `createAdapter`
changeColor: (colors, [newColor, index]: [string, number]) =>
colors.map((color, i) => i === index ? newColor : color),
selectors: {
colors: state => state.map(color => ({
value: color,
name: color.charAt(0).toUpperCase() + color.slice(1),
})),
},
});
store = createStore('colors', ['aqua', 'aqua', 'aqua'], this.adapter);
}
Can you see how this straightforward shift unlocks the ability to manage significantly more complexity? We might effortlessly spin up multiple stores for distinct sets of colors that operate independently:
favoriteStore = createStore('colors.favorite', ['aqua', 'aqua', 'aqua'], this.adapter);
dislikedStore = createStore('colors.disliked', ['orange', 'orange', 'orange'], this.adapter);
neutralStore = createStore('colors.neutral', ['purple', 'purple', 'purple'], this.adapter);
Admittedly, it's not a thing of beauty, but that's a design concern, not ours to solve.
I chose to name the state manager object adapter because NgRx refers to its entity state manager as entityAdapter.
NgRx/Entity is typically regarded as an advanced NgRx concept, yet in our scenario, building our own colors adapter turned out to be the most straightforward and compact approach to modeling the colors state. We simply defined it directly at first. Compact code offers flexibility.
Eventually, virtually any web application might expand to a point where the same type of state appears multiple times on a single page. I've hit that wall a few times myself, and every instance was painful. Here we're just working with colors, but on one larger project, a designer once asked me to add a second filtered, paginated data grid alongside the first on the same page. Suddenly, our state management approach—which had tightly wound together specific user interactions, particular state instances, and the business logic for both updates and derived data—turned into a substantial refactoring effort. Action payloads needed redesign, and the state object had to be restructured with more depth. It consumed time and left the code messier and trickier to follow.
Choosing anything other than state adapters for handling state logic dead-ends syntactically. This issue may not surface as frequently as others, but when it does, it's usually without warning. Given that, whatever state management library we pick, our state logic should remain bundled together without becoming overly entangled in event handlers or side-effect logic. In my view, the adapter syntax stands as the best option.
createAdapter is something I've put together that offers type inference. Putting it into practice is fairly straightforward, but at its core, all you truly require is an object holding state transition functions and selectors. No library is necessary for that.
Up next, we'll turn our attention to asynchronous state sources.

