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
The main devtools service providing programmatic access to debugging functionality including time-travel operations, state inspection, and recording controls.
Core service that manages devtools state and provides debugging methods.
/**
* Main devtools service implementing Observer pattern for action handling
*/
class StoreDevtools implements Observer<any>, OnDestroy {
/** Action dispatcher for devtools actions */
dispatcher: ActionsSubject;
/** Observable of enhanced state with devtools metadata */
liftedState: Observable<LiftedState>;
/** Observable of current application state */
state: StateObservable;
/**
* Dispatch an action through the devtools system
* @param action - Action to dispatch
*/
dispatch(action: Action): void;
/**
* Observer interface method for handling next action
* @param action - Action to handle
*/
next(action: any): void;
/**
* Observer interface method for handling errors
* @param error - Error to handle
*/
error(error: any): void;
/**
* Observer interface method for completion
*/
complete(): void;
/**
* Perform a user action through the devtools system
* @param action - User action to perform
*/
performAction(action: any): void;
/**
* Refresh the devtools state
*/
refresh(): void;
/**
* Reset the devtools to initial state
*/
reset(): void;
/**
* Rollback to the previous committed state
*/
rollback(): void;
/**
* Commit the current state as the new baseline
*/
commit(): void;
/**
* Remove all skipped actions from history
*/
sweep(): void;
/**
* Toggle an action between active and inactive state
* @param id - ID of the action to toggle
*/
toggleAction(id: number): void;
/**
* Jump to a specific action in the history
* @param actionId - ID of the action to jump to
*/
jumpToAction(actionId: number): void;
/**
* Jump to a specific state index in the history
* @param index - Index of the state to jump to
*/
jumpToState(index: number): void;
/**
* Import a previously exported state
* @param nextLiftedState - Lifted state to import
*/
importState(nextLiftedState: any): void;
/**
* Lock or unlock state changes
* @param status - True to lock changes, false to unlock
*/
lockChanges(status: boolean): void;
/**
* Pause or resume recording of actions
* @param status - True to pause recording, false to resume
*/
pauseRecording(status: boolean): void;
/**
* Cleanup method called when service is destroyed
*/
ngOnDestroy(): void;
}Usage Examples:
import { Component, OnInit } from "@angular/core";
import { StoreDevtools } from "@ngrx/store-devtools";
@Component({
selector: "app-debug-controls",
template: `
<button (click)="reset()">Reset</button>
<button (click)="commit()">Commit</button>
<button (click)="rollback()">Rollback</button>
<button (click)="toggleRecording()">
{{ isPaused ? 'Resume' : 'Pause' }} Recording
</button>
`,
})
export class DebugControlsComponent implements OnInit {
isPaused = false;
constructor(private devtools: StoreDevtools) {}
ngOnInit() {
// Subscribe to lifted state to monitor devtools state
this.devtools.liftedState.subscribe((liftedState) => {
this.isPaused = liftedState.isPaused;
console.log("Current action count:", liftedState.nextActionId);
});
}
reset() {
this.devtools.reset();
}
commit() {
this.devtools.commit();
}
rollback() {
this.devtools.rollback();
}
toggleRecording() {
this.devtools.pauseRecording(!this.isPaused);
}
}Specialized dispatcher extending ActionsSubject for devtools operations.
/**
* Injectable dispatcher service extending ActionsSubject for devtools
*/
class DevtoolsDispatcher extends ActionsSubject {}Service managing connection to Redux DevTools browser extension.
/**
* Service for integrating with Redux DevTools browser extension
*/
class DevtoolsExtension {
/** Observable of actions from the extension */
actions$: Observable<any>;
/** Observable of lifted actions from the extension */
liftedActions$: Observable<any>;
/** Observable indicating when extension starts */
start$: Observable<any>;
/**
* Notify the extension of state changes
* @param action - Action that caused the change
* @param state - New lifted state
*/
notify(action: any, state: LiftedState): void;
}
/**
* Interface for Redux DevTools extension connection
*/
interface ReduxDevtoolsExtensionConnection {
/** Subscribe to extension changes */
subscribe(listener: (change: any) => void): void;
/** Unsubscribe from extension changes */
unsubscribe(): void;
/** Send action and state to extension */
send(action: any, state: any): void;
/** Initialize extension with state */
init(state: any): void;
/** Send error message to extension */
error(message: string): void;
}
/**
* Redux DevTools browser extension interface
*/
interface ReduxDevtoolsExtension {
connect(options?: any): ReduxDevtoolsExtensionConnection;
}Factory function for creating state observable from devtools service.
/**
* Create state observable from devtools service
* @param devtools - StoreDevtools service instance
* @returns StateObservable for current application state
*/
function createStateObservable(devtools: StoreDevtools): StateObservable;Zone configuration utilities for controlling Angular zone behavior with devtools.
/**
* Zone configuration type for devtools connection
*/
type ZoneConfig =
| { connectInZone: true; ngZone: NgZone }
| { connectInZone: false; ngZone: null };
/**
* Inject zone configuration based on connectInZone setting
* @param connectInZone - Whether to connect within Angular zone
* @returns Zone configuration object
*/
function injectZoneConfig(connectInZone: boolean): ZoneConfig;Advanced Usage Example:
import { Injectable } from "@angular/core";
import { StoreDevtools } from "@ngrx/store-devtools";
import { map, filter, take } from "rxjs/operators";
@Injectable({
providedIn: "root",
})
export class CustomDebuggingService {
constructor(private devtools: StoreDevtools) {}
// Monitor specific action types
watchActionType(actionType: string) {
return this.devtools.liftedState.pipe(
map((liftedState) => liftedState.actionsById),
map((actions) =>
Object.values(actions).filter((action) => action.action.type === actionType)
)
);
}
// Get current error state
getCurrentErrors() {
return this.devtools.liftedState.pipe(
map((liftedState) => liftedState.computedStates),
map((states) => states.filter((state) => state.error)),
filter((errorStates) => errorStates.length > 0)
);
}
// Jump to first occurrence of action type
jumpToFirstAction(actionType: string) {
this.devtools.liftedState.pipe(take(1)).subscribe((liftedState) => {
const actionId = Object.keys(liftedState.actionsById).find(
(id) => liftedState.actionsById[parseInt(id)].action.type === actionType
);
if (actionId) {
this.devtools.jumpToAction(parseInt(actionId));
}
});
}
// Export current devtools state
exportState() {
return this.devtools.liftedState.pipe(
take(1),
map((liftedState) => JSON.stringify(liftedState, null, 2))
);
}
// Import devtools state from JSON
importStateFromJson(jsonState: string) {
try {
const liftedState = JSON.parse(jsonState);
this.devtools.importState(liftedState);
} catch (error) {
console.error("Failed to import state:", error);
}
}
}