Skip to content

⭐ Composition Over Inheritance

Terrence Daniels edited this page Aug 14, 2026 · 1 revision

Six real ngrx classes across store and effectsStore, ActionsSubject, ReducerManager, State, ScannedActionsSubject, EffectSources — extend RxJS's Observable/Subject types directly in the real upstream source. That's a genuine Interface Segregation violation, not a style nitpick: it hands every consumer the entire RxJS operator surface (pipe, lift, toPromise, ...) when each class's actual contract is much narrower. Store even has to override lift() purely to stop RxJS's own internal machinery from silently downgrading it back to a plain Observable mid-chain — direct evidence the inheritance doesn't fit.

Finding all six took three passes, not one: the first sweep caught the three obvious ones (Store, ActionsSubject, ReducerManager); a second pass — re-running the same grep after the first round landed — caught two more (State, StateObservable); a third pass, a deliberate full-module audit of every extends occurrence (191 occurrences, 19 files), caught the last one (ScannedActionsSubject, a multi-line declaration the earlier grep missed). Full writeup, including what this cost (Store losing the Observer interface, ~40 rewritten call sites, 2 deleted tests) and what was deliberately left alone (ReducerObservable, Actions — both correctly fit their inheritance), is in docs/architecture.md.

The redesign shipped before effects/entity/router-store/store-devtools/data were added, specifically so each one only had to adapt to it once. It did — see Effects, Router Store, Store Devtools, Data for the ripple each one hit.

Clone this wiki locally