Developer tools for @ngrx/store providing comprehensive debugging and time-travel capabilities for Angular applications.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Enhanced state structures and type definitions that provide devtools metadata alongside application state, enabling comprehensive debugging capabilities.
The primary enhanced state structure containing both application state and devtools metadata.
/**
* Enhanced state containing devtools metadata alongside application state
*/
interface LiftedState {
/** Monitor's internal state for custom monitors */
monitorState: any;
/** Counter for generating unique action IDs */
nextActionId: number;
/** Map of action IDs to lifted actions */
actionsById: LiftedActions;
/** Array of action IDs that are staged for execution */
stagedActionIds: number[];
/** Array of action IDs that have been skipped */
skippedActionIds: number[];
/** The last committed application state */
committedState: any;
/** Index of the currently displayed state */
currentStateIndex: number;
/** Array of computed states with their results */
computedStates: ComputedState[];
/** Whether state changes are locked */
isLocked: boolean;
/** Whether action recording is paused */
isPaused: boolean;
}Usage Example:
import { Injectable } from "@angular/core";
import { StoreDevtools, LiftedState } from "@ngrx/store-devtools";
import { map, filter } from "rxjs/operators";
@Injectable()
export class StateAnalysisService {
constructor(private devtools: StoreDevtools) {}
// Monitor state changes
monitorStateChanges() {
return this.devtools.liftedState.pipe(
map((liftedState: LiftedState) => ({
totalActions: liftedState.nextActionId,
currentIndex: liftedState.currentStateIndex,
skippedCount: liftedState.skippedActionIds.length,
isLocked: liftedState.isLocked,
isPaused: liftedState.isPaused,
}))
);
}
// Get current application state
getCurrentAppState() {
return this.devtools.liftedState.pipe(
map((liftedState: LiftedState) => {
const currentState = liftedState.computedStates[liftedState.currentStateIndex];
return currentState ? currentState.state : liftedState.committedState;
})
);
}
// Check for state errors
getStateErrors() {
return this.devtools.liftedState.pipe(
map((liftedState: LiftedState) =>
liftedState.computedStates.filter(state => state.error)
),
filter(errorStates => errorStates.length > 0)
);
}
}Represents a single computed state with potential error information.
/**
* Single computed state containing the result of applying actions
*/
interface ComputedState {
/** The computed application state */
state: any;
/** Error that occurred during state computation, if any */
error: any;
}Wrapper for actions that includes devtools metadata.
/**
* Wrapper for actions that includes devtools metadata
*/
interface LiftedAction {
/** Action type string */
type: string;
/** The original action object */
action: Action;
}Collection of lifted actions indexed by their IDs.
/**
* Map of action IDs to lifted actions
*/
interface LiftedActions {
[id: number]: LiftedAction;
}Usage Example:
import { Injectable } from "@angular/core";
import { StoreDevtools, LiftedActions, LiftedAction } from "@ngrx/store-devtools";
@Injectable()
export class ActionAnalysisService {
constructor(private devtools: StoreDevtools) {}
// Get actions by type
getActionsByType(actionType: string) {
return this.devtools.liftedState.pipe(
map(liftedState => liftedState.actionsById),
map((actionsById: LiftedActions) =>
Object.values(actionsById).filter((liftedAction: LiftedAction) =>
liftedAction.action.type === actionType
)
)
);
}
// Get action frequency statistics
getActionFrequencyStats() {
return this.devtools.liftedState.pipe(
map(liftedState => liftedState.actionsById),
map((actionsById: LiftedActions) => {
const frequency: { [actionType: string]: number } = {};
Object.values(actionsById).forEach((liftedAction: LiftedAction) => {
const type = liftedAction.action.type;
frequency[type] = (frequency[type] || 0) + 1;
});
return frequency;
})
);
}
// Find actions with specific payload properties
findActionsWithPayload(propertyName: string, propertyValue: any) {
return this.devtools.liftedState.pipe(
map(liftedState => liftedState.actionsById),
map((actionsById: LiftedActions) =>
Object.entries(actionsById)
.filter(([id, liftedAction]) => {
const payload = (liftedAction.action as any).payload;
return payload && payload[propertyName] === propertyValue;
})
.map(([id, liftedAction]) => ({ id: parseInt(id), action: liftedAction }))
)
);
}
}Additional action types that work with the devtools system.
/**
* NgRx store initialization action type
*/
type InitAction = {
readonly type: typeof INIT;
};
/**
* NgRx store reducer update action type
*/
type UpdateReducerAction = {
readonly type: typeof UPDATE;
};
/**
* Union of core NgRx actions
*/
type CoreActions = InitAction | UpdateReducerAction;
/**
* Union of all devtools and core actions
*/
type Actions = DevtoolsActions.All | CoreActions;Constants related to state recomputation.
/** Constant for recompute action type */
const RECOMPUTE = '@ngrx/store-devtools/recompute';
/** Recompute action object */
const RECOMPUTE_ACTION = { type: RECOMPUTE };
/** NgRx store initialization action object */
const INIT_ACTION = { type: INIT };Advanced Usage Example:
import { Injectable } from "@angular/core";
import { Store } from "@ngrx/store";
import {
StoreDevtools,
LiftedState,
ComputedState,
LiftedAction,
RECOMPUTE_ACTION
} from "@ngrx/store-devtools";
import { withLatestFrom, tap, map, distinctUntilChanged, take } from "rxjs/operators";
@Injectable()
export class AdvancedStateService {
constructor(
private store: Store,
private devtools: StoreDevtools
) {}
// Compare states at different points in time
compareStates(index1: number, index2: number) {
return this.devtools.liftedState.pipe(
map((liftedState: LiftedState) => {
const state1 = liftedState.computedStates[index1];
const state2 = liftedState.computedStates[index2];
return {
state1: state1 ? state1.state : null,
state2: state2 ? state2.state : null,
error1: state1 ? state1.error : null,
error2: state2 ? state2.error : null,
hasErrors: (state1?.error || state2?.error) !== null,
};
})
);
}
// Track state size changes
trackStateSizeChanges() {
return this.devtools.liftedState.pipe(
map((liftedState: LiftedState) => {
const currentState = liftedState.computedStates[liftedState.currentStateIndex];
if (!currentState) return 0;
return JSON.stringify(currentState.state).length;
}),
distinctUntilChanged(),
tap(size => console.log(`State size changed to: ${size} characters`))
);
}
// Export simplified state history
exportStateHistory() {
return this.devtools.liftedState.pipe(
take(1),
map((liftedState: LiftedState) => {
const history = liftedState.stagedActionIds.map(actionId => {
const liftedAction = liftedState.actionsById[actionId];
const computedState = liftedState.computedStates.find(
(state, index) => liftedState.stagedActionIds[index] === actionId
);
return {
actionId,
actionType: liftedAction?.action.type,
hasError: computedState?.error !== null,
timestamp: (liftedAction?.action as any).timestamp || Date.now(),
};
});
return {
totalActions: liftedState.nextActionId,
currentIndex: liftedState.currentStateIndex,
isLocked: liftedState.isLocked,
isPaused: liftedState.isPaused,
history,
};
})
);
}
// Force recompute of all states
forceRecompute() {
this.devtools.dispatch(RECOMPUTE_ACTION);
}
// Get performance metrics
getPerformanceMetrics() {
return this.devtools.liftedState.pipe(
map((liftedState: LiftedState) => {
const totalActions = liftedState.nextActionId;
const errorCount = liftedState.computedStates.filter(state => state.error).length;
const skippedCount = liftedState.skippedActionIds.length;
return {
totalActions,
errorCount,
skippedCount,
activeActions: totalActions - skippedCount,
errorRate: totalActions > 0 ? (errorCount / totalActions) * 100 : 0,
memoryUsage: JSON.stringify(liftedState).length,
};
})
);
}
}