11import  {  inject  }  from  '@angular/core' ; 
2- import  {  Events  }  from  '@ngrx/signals/events' ; 
2+ import  {  Dispatcher ,   Events  }  from  '@ngrx/signals/events' ; 
33import  {  createDevtoolsFeature  }  from  '../internal/devtools-feature' ; 
4+ import  {  GlitchTrackerService  }  from  '../internal/glitch-tracker.service' ; 
45
56/** 
67 * Automatically infers DevTools action names from NgRx SignalStore events. 
@@ -9,13 +10,59 @@ import { createDevtoolsFeature } from '../internal/devtools-feature';
910 * the event's type as the upcoming DevTools action name. When the corresponding 
1011 * reducer mutates state, the DevTools sync will use that name instead of 
1112 * the default "Store Update". 
13+  * 
14+  * By default (withGlitchTracking = true), the `GlitchTrackerService` is used to capture 
15+  * all intermediate updates (glitched states). To use the default, glitch-free tracker 
16+  * and synchronize only stable state transitions, set `withGlitchTracking` to `false`. 
17+  * 
18+  * @param  {{ withGlitchTracking?: boolean } } [options] Options to configure tracking behavior. 
19+  * @param  {boolean } [options.withGlitchTracking=true] Enable capturing intermediate (glitched) state updates. 
20+  * @returns  Devtools feature enabling events-based action naming; glitched tracking is enabled by default. 
21+  * Set `withGlitchTracking: false` to use glitch-free tracking instead. 
22+  * @example  
23+  * // Capture intermediate updates (default) 
24+  * withDevtools('counter', withEventsTracking()); 
25+  * @example  
26+  * // Glitch-free tracking (only stable transitions) 
27+  * withDevtools('counter', withEventsTracking({ withGlitchTracking: false })); 
28+  * @see  withGlitchTracking 
1229 */ 
13- export  function  withEventsTracking ( )  { 
30+ export  function  withEventsTracking ( 
31+   options : {  withGlitchTracking : boolean  }  =  {  withGlitchTracking : true  } , 
32+ )  { 
33+   const  useGlitchTracking  =  options . withGlitchTracking  ===  true ; 
1434  return  createDevtoolsFeature ( { 
35+     tracker : useGlitchTracking  ? GlitchTrackerService  : undefined , 
1536    eventsTracking : true , 
1637    onInit : ( {  trackEvents } )  =>  { 
17-       const  events  =  inject ( Events ) ; 
18-       trackEvents ( events . on ( ) ) ; 
38+       if  ( useGlitchTracking )  { 
39+         trackEvents ( getReducerEvents ( ) . on ( ) ) ; 
40+       }  else  { 
41+         trackEvents ( inject ( Events ) . on ( ) ) ; 
42+       } 
1943    } , 
2044  } ) ; 
2145} 
46+ 
47+ /** 
48+  * Returns the synchronous reducer event stream exposed by the dispatcher. 
49+  * 
50+  * NgRx's `Dispatcher` delivers events to `ReducerEvents` immediately but feeds 
51+  * the public `Events` stream via `queueScheduler`. When `GlitchTrackerService` 
52+  * captures the state change synchronously, the queued `Events` emission arrives 
53+  * too late and DevTools records the update as `Store Update`. Tapping into the 
54+  * reducer stream keeps event names and state changes aligned on the same tick. 
55+  * 
56+  * TODO(@ngrx): expose a synchronous events API (similar to what `withReducer` uses) 
57+  * so consumers do not need to reach into dispatcher internals. 
58+  */ 
59+ function  getReducerEvents ( )  { 
60+   type  ReducerEventsLike  =  { 
61+     on ( ) : ReturnType < Dispatcher [ 'events' ] [ 'on' ] > ; 
62+   } ; 
63+ 
64+   const  dispatcher  =  inject ( Dispatcher )  as  unknown  as  { 
65+     reducerEvents : ReducerEventsLike ; 
66+   } ; 
67+   return  dispatcher . reducerEvents ; 
68+ } 
0 commit comments