platform-main — effects runtime data flow

One subscribe-before-push ordering the whole effects system quietly depends on.

EffectsRootModule's constructor runs exactly once, and the order of its three statements isn't incidental — get it wrong and every root-registered effect would silently never fire, with no error anywhere to point at why. Traced end to end during the code review of effects_root_module.ts.

EffectsRootModule Constructor, In Order

1
runner.start()
EffectsRunner subscribes to effectSources.toActions() and dispatches every emitted action to the Store. Guarded by a stored Subscription so a second start() is a no-op.
2
for (effect of rootEffectsInstances) sources.addEffects(effect)
Pushes every root-registered effects instance into EffectSources' internal stream.
3
store.dispatch({ type: ROOT_EFFECTS_INIT })
Fires once, after registration — the signal effects can key off "root effects are ready."

Why step 1 has to come before step 2: EffectSources composes a plain RxJS Subject for its internal sources$ — not a ReplaySubject. A plain Subject drops any next() call made before a subscriber exists. If the loop in step 2 ran first, every addEffects() call would push into sources$ with nobody listening yet, and toActions() would never see any of it. Starting the runner first guarantees the subscription exists before anything is pushed in.

EffectSources.toActions() Pipeline

sources$ (Subject) → groupBy(class prototype) per-class group → groupBy(ngrxOnIdentifyEffects key) per-identifier group → exhaustMap(mergeEffects) EffectNotification → dematerialize + filter Action store.dispatch()