platform-main — composition-over-inheritance redesign
The real @ngrx/store/@ngrx/effects extend RxJS's
Observable/ Subject/BehaviorSubject
directly on six classes across two modules — a genuine Interface Segregation
violation, not a style nitpick. Every consumer inherits the entire RxJS
operator surface (pipe, lift,
toPromise, forEach…) when each class's actual
contract is far narrower. Fixed one class at a time, in dependency order,
fully verified before moving to the next.
extends BehaviorSubject<Action>
next/subscribe/error/complete/asObservable
only — no pipe, not instanceof Observable
extends BehaviorSubject<ActionReducer>
ReducerObservable DI token still
exposes the Observable view for consumers that need one
extends Observable<T> implements Observer<Action>, overrides lift() to stop RxJS silently downgrading it
select() keeps its full overload set, returns a plain
Observable off a new state$ field —
next/error/complete/subscribe/pipe
all removed, a real capability cut
extends BehaviorSubject<any>
BehaviorSubject, exposes
state$/state/a value getter
extends Subject<Action> implements OnDestroy
Subject; found in round 3 of the audit —
a multi-line class declaration the first two grep passes missed
extends Subject<any>
Subject; contract narrowed to
addEffects() in, toActions() out
Each class now holds its RxJS subject privately instead of extending it,
exposing only the surface its actual contract needs. No
override-the-base-class special case left behind —
Store's old lift() override, the symptom that
flagged the problem in the first place, is gone because the cause is
gone, not patched around.
Store losing the Observer interface is a real
behavior change: ~40 call sites across 6 spec files rewritten, 2 tests
deleted outright because the capability they tested no longer exists to
test. Every module added afterward that depends on store —
entity, effects, router-store,
store-devtools, data — hit the same ripple at
least once, caught by the build, not by inspection.
ReducerObservable and
Actions (effects) both still extend Observable —
their entire contract is being pipeable, with no unrelated method
bolted on, so there's no interface to segregate. Composition was applied to
six classes because six classes had the actual violation, not as a blanket
"never extend RxJS" rule — the two exceptions are the evidence the decision
was reasoned, not reflexive. See
docs/architecture.md
for the full ADR write-up.