0
# State Management Types
1
2
Enhanced state structures and type definitions that provide devtools metadata alongside application state, enabling comprehensive debugging capabilities.
3
4
## Capabilities
5
6
### Lifted State Interface
7
8
The primary enhanced state structure containing both application state and devtools metadata.
9
10
```typescript { .api }
11
/**
12
* Enhanced state containing devtools metadata alongside application state
13
*/
14
interface LiftedState {
15
/** Monitor's internal state for custom monitors */
16
monitorState: any;
17
/** Counter for generating unique action IDs */
18
nextActionId: number;
19
/** Map of action IDs to lifted actions */
20
actionsById: LiftedActions;
21
/** Array of action IDs that are staged for execution */
22
stagedActionIds: number[];
23
/** Array of action IDs that have been skipped */
24
skippedActionIds: number[];
25
/** The last committed application state */
26
committedState: any;
27
/** Index of the currently displayed state */
28
currentStateIndex: number;
29
/** Array of computed states with their results */
30
computedStates: ComputedState[];
31
/** Whether state changes are locked */
32
isLocked: boolean;
33
/** Whether action recording is paused */
34
isPaused: boolean;
35
}
36
```
37
38
**Usage Example:**
39
40
```typescript
41
import { Injectable } from "@angular/core";
42
import { StoreDevtools, LiftedState } from "@ngrx/store-devtools";
43
import { map, filter } from "rxjs/operators";
44
45
@Injectable()
46
export class StateAnalysisService {
47
constructor(private devtools: StoreDevtools) {}
48
49
// Monitor state changes
50
monitorStateChanges() {
51
return this.devtools.liftedState.pipe(
52
map((liftedState: LiftedState) => ({
53
totalActions: liftedState.nextActionId,
54
currentIndex: liftedState.currentStateIndex,
55
skippedCount: liftedState.skippedActionIds.length,
56
isLocked: liftedState.isLocked,
57
isPaused: liftedState.isPaused,
58
}))
59
);
60
}
61
62
// Get current application state
63
getCurrentAppState() {
64
return this.devtools.liftedState.pipe(
65
map((liftedState: LiftedState) => {
66
const currentState = liftedState.computedStates[liftedState.currentStateIndex];
67
return currentState ? currentState.state : liftedState.committedState;
68
})
69
);
70
}
71
72
// Check for state errors
73
getStateErrors() {
74
return this.devtools.liftedState.pipe(
75
map((liftedState: LiftedState) =>
76
liftedState.computedStates.filter(state => state.error)
77
),
78
filter(errorStates => errorStates.length > 0)
79
);
80
}
81
}
82
```
83
84
### Computed State Interface
85
86
Represents a single computed state with potential error information.
87
88
```typescript { .api }
89
/**
90
* Single computed state containing the result of applying actions
91
*/
92
interface ComputedState {
93
/** The computed application state */
94
state: any;
95
/** Error that occurred during state computation, if any */
96
error: any;
97
}
98
```
99
100
### Lifted Action Interface
101
102
Wrapper for actions that includes devtools metadata.
103
104
```typescript { .api }
105
/**
106
* Wrapper for actions that includes devtools metadata
107
*/
108
interface LiftedAction {
109
/** Action type string */
110
type: string;
111
/** The original action object */
112
action: Action;
113
}
114
```
115
116
### Lifted Actions Map
117
118
Collection of lifted actions indexed by their IDs.
119
120
```typescript { .api }
121
/**
122
* Map of action IDs to lifted actions
123
*/
124
interface LiftedActions {
125
[id: number]: LiftedAction;
126
}
127
```
128
129
**Usage Example:**
130
131
```typescript
132
import { Injectable } from "@angular/core";
133
import { StoreDevtools, LiftedActions, LiftedAction } from "@ngrx/store-devtools";
134
135
@Injectable()
136
export class ActionAnalysisService {
137
constructor(private devtools: StoreDevtools) {}
138
139
// Get actions by type
140
getActionsByType(actionType: string) {
141
return this.devtools.liftedState.pipe(
142
map(liftedState => liftedState.actionsById),
143
map((actionsById: LiftedActions) =>
144
Object.values(actionsById).filter((liftedAction: LiftedAction) =>
145
liftedAction.action.type === actionType
146
)
147
)
148
);
149
}
150
151
// Get action frequency statistics
152
getActionFrequencyStats() {
153
return this.devtools.liftedState.pipe(
154
map(liftedState => liftedState.actionsById),
155
map((actionsById: LiftedActions) => {
156
const frequency: { [actionType: string]: number } = {};
157
158
Object.values(actionsById).forEach((liftedAction: LiftedAction) => {
159
const type = liftedAction.action.type;
160
frequency[type] = (frequency[type] || 0) + 1;
161
});
162
163
return frequency;
164
})
165
);
166
}
167
168
// Find actions with specific payload properties
169
findActionsWithPayload(propertyName: string, propertyValue: any) {
170
return this.devtools.liftedState.pipe(
171
map(liftedState => liftedState.actionsById),
172
map((actionsById: LiftedActions) =>
173
Object.entries(actionsById)
174
.filter(([id, liftedAction]) => {
175
const payload = (liftedAction.action as any).payload;
176
return payload && payload[propertyName] === propertyValue;
177
})
178
.map(([id, liftedAction]) => ({ id: parseInt(id), action: liftedAction }))
179
)
180
);
181
}
182
}
183
```
184
185
### Core Action Types
186
187
Additional action types that work with the devtools system.
188
189
```typescript { .api }
190
/**
191
* NgRx store initialization action type
192
*/
193
type InitAction = {
194
readonly type: typeof INIT;
195
};
196
197
/**
198
* NgRx store reducer update action type
199
*/
200
type UpdateReducerAction = {
201
readonly type: typeof UPDATE;
202
};
203
204
/**
205
* Union of core NgRx actions
206
*/
207
type CoreActions = InitAction | UpdateReducerAction;
208
209
/**
210
* Union of all devtools and core actions
211
*/
212
type Actions = DevtoolsActions.All | CoreActions;
213
```
214
215
### Recompute Constants
216
217
Constants related to state recomputation.
218
219
```typescript { .api }
220
/** Constant for recompute action type */
221
const RECOMPUTE = '@ngrx/store-devtools/recompute';
222
223
/** Recompute action object */
224
const RECOMPUTE_ACTION = { type: RECOMPUTE };
225
226
/** NgRx store initialization action object */
227
const INIT_ACTION = { type: INIT };
228
```
229
230
**Advanced Usage Example:**
231
232
```typescript
233
import { Injectable } from "@angular/core";
234
import { Store } from "@ngrx/store";
235
import {
236
StoreDevtools,
237
LiftedState,
238
ComputedState,
239
LiftedAction,
240
RECOMPUTE_ACTION
241
} from "@ngrx/store-devtools";
242
import { withLatestFrom, tap, map, distinctUntilChanged, take } from "rxjs/operators";
243
244
@Injectable()
245
export class AdvancedStateService {
246
constructor(
247
private store: Store,
248
private devtools: StoreDevtools
249
) {}
250
251
// Compare states at different points in time
252
compareStates(index1: number, index2: number) {
253
return this.devtools.liftedState.pipe(
254
map((liftedState: LiftedState) => {
255
const state1 = liftedState.computedStates[index1];
256
const state2 = liftedState.computedStates[index2];
257
258
return {
259
state1: state1 ? state1.state : null,
260
state2: state2 ? state2.state : null,
261
error1: state1 ? state1.error : null,
262
error2: state2 ? state2.error : null,
263
hasErrors: (state1?.error || state2?.error) !== null,
264
};
265
})
266
);
267
}
268
269
// Track state size changes
270
trackStateSizeChanges() {
271
return this.devtools.liftedState.pipe(
272
map((liftedState: LiftedState) => {
273
const currentState = liftedState.computedStates[liftedState.currentStateIndex];
274
if (!currentState) return 0;
275
276
return JSON.stringify(currentState.state).length;
277
}),
278
distinctUntilChanged(),
279
tap(size => console.log(`State size changed to: ${size} characters`))
280
);
281
}
282
283
// Export simplified state history
284
exportStateHistory() {
285
return this.devtools.liftedState.pipe(
286
take(1),
287
map((liftedState: LiftedState) => {
288
const history = liftedState.stagedActionIds.map(actionId => {
289
const liftedAction = liftedState.actionsById[actionId];
290
const computedState = liftedState.computedStates.find(
291
(state, index) => liftedState.stagedActionIds[index] === actionId
292
);
293
294
return {
295
actionId,
296
actionType: liftedAction?.action.type,
297
hasError: computedState?.error !== null,
298
timestamp: (liftedAction?.action as any).timestamp || Date.now(),
299
};
300
});
301
302
return {
303
totalActions: liftedState.nextActionId,
304
currentIndex: liftedState.currentStateIndex,
305
isLocked: liftedState.isLocked,
306
isPaused: liftedState.isPaused,
307
history,
308
};
309
})
310
);
311
}
312
313
// Force recompute of all states
314
forceRecompute() {
315
this.devtools.dispatch(RECOMPUTE_ACTION);
316
}
317
318
// Get performance metrics
319
getPerformanceMetrics() {
320
return this.devtools.liftedState.pipe(
321
map((liftedState: LiftedState) => {
322
const totalActions = liftedState.nextActionId;
323
const errorCount = liftedState.computedStates.filter(state => state.error).length;
324
const skippedCount = liftedState.skippedActionIds.length;
325
326
return {
327
totalActions,
328
errorCount,
329
skippedCount,
330
activeActions: totalActions - skippedCount,
331
errorRate: totalActions > 0 ? (errorCount / totalActions) * 100 : 0,
332
memoryUsage: JSON.stringify(liftedState).length,
333
};
334
})
335
);
336
}
337
}
338
```