ngrx-devtool-debugger

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

NgRx DevTool Debugger

NgRx DevTool 调试工具

Skill by ara.so — Devtools Skills collection.
NgRx DevTool is a comprehensive debugging and visualization tool for NgRx state management in Angular applications. It provides real-time action monitoring, effect tracking, state visualization with diff viewer, and performance metrics without requiring browser extensions. The tool runs a separate WebSocket server that your Angular app connects to, displaying all NgRx activity in a dedicated UI.
ara.so 提供的技能 — 开发工具技能合集。
NgRx DevTool 是一款用于Angular应用中NgRx状态管理的综合性调试与可视化工具。它无需浏览器扩展,即可提供实时动作监控、效果追踪、带差异查看器的状态可视化以及性能指标。该工具运行一个独立的WebSocket服务器,你的Angular应用可连接至该服务器,在专用UI中展示所有NgRx活动。

Installation

安装

Install the package as a development dependency:
bash
npm install --save-dev @amadeus-it-group/ngrx-devtool
Or with yarn:
bash
yarn add -D @amadeus-it-group/ngrx-devtool
将该包作为开发依赖安装:
bash
npm install --save-dev @amadeus-it-group/ngrx-devtool
或使用yarn:
bash
yarn add -D @amadeus-it-group/ngrx-devtool

Basic Setup

基础配置

1. Configure Your Angular Application

1. 配置你的Angular应用

Add the DevTool provider and meta-reducer to your application configuration:
typescript
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import { 
  provideNgrxDevTool, 
  createDevToolMetaReducer 
} from '@amadeus-it-group/ngrx-devtool';
import * as fromRoot from './store/reducers';
import { AppEffects } from './store/effects/app.effects';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(
      fromRoot.reducers,
      { 
        metaReducers: [createDevToolMetaReducer()] 
      }
    ),
    provideEffects([AppEffects]),
    provideNgrxDevTool({
      wsUrl: 'ws://localhost:4000',
      trackEffects: true,
      enabled: true
    })
  ]
};
将DevTool提供者和元 reducer 添加到应用配置中:
typescript
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import { 
  provideNgrxDevTool, 
  createDevToolMetaReducer 
} from '@amadeus-it-group/ngrx-devtool';
import * as fromRoot from './store/reducers';
import { AppEffects } from './store/effects/app.effects';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(
      fromRoot.reducers,
      { 
        metaReducers: [createDevToolMetaReducer()] 
      }
    ),
    provideEffects([AppEffects]),
    provideNgrxDevTool({
      wsUrl: 'ws://localhost:4000',
      trackEffects: true,
      enabled: true
    })
  ]
};

2. Module-Based Configuration (Legacy)

2. 基于模块的配置(旧版)

If using NgModule instead of standalone components:
typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { 
  NgRxDevToolModule, 
  createDevToolMetaReducer 
} from '@amadeus-it-group/ngrx-devtool';
import { reducers } from './store/reducers';
import { AppEffects } from './store/effects/app.effects';

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, {
      metaReducers: [createDevToolMetaReducer()]
    }),
    EffectsModule.forRoot([AppEffects]),
    NgRxDevToolModule.forRoot({
      wsUrl: 'ws://localhost:4000',
      trackEffects: true
    })
  ]
})
export class AppModule {}
如果使用NgModule而非独立组件:
typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { 
  NgRxDevToolModule, 
  createDevToolMetaReducer 
} from '@amadeus-it-group/ngrx-devtool';
import { reducers } from './store/reducers';
import { AppEffects } from './store/effects/app.effects';

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, {
      metaReducers: [createDevToolMetaReducer()]
    }),
    EffectsModule.forRoot([AppEffects]),
    NgRxDevToolModule.forRoot({
      wsUrl: 'ws://localhost:4000',
      trackEffects: true
    })
  ]
})
export class AppModule {}

CLI Commands

CLI命令

Start the DevTool Server

启动DevTool服务器

The primary command to launch both the WebSocket server and UI:
bash
npx ngrx-devtool
This starts:
  • WebSocket server on
    ws://localhost:4000
  • UI server on
    http://localhost:3000
启动WebSocket服务器和UI的主要命令:
bash
npx ngrx-devtool
这将启动:
  • 位于
    ws://localhost:4000
    的WebSocket服务器
  • 位于
    http://localhost:3000
    的UI服务器

Custom Port Configuration

自定义端口配置

bash
undefined
bash
undefined

Change WebSocket port

修改WebSocket端口

npx ngrx-devtool --ws-port 5000
npx ngrx-devtool --ws-port 5000

Change UI port

修改UI端口

npx ngrx-devtool --ui-port 8080
npx ngrx-devtool --ui-port 8080

Custom ports for both

同时自定义两个端口

npx ngrx-devtool --ws-port 5000 --ui-port 8080
undefined
npx ngrx-devtool --ws-port 5000 --ui-port 8080
undefined

Server Only Mode

仅服务器模式

Run only the WebSocket server without the UI:
bash
npx ngrx-devtool --server-only
仅运行WebSocket服务器,不启动UI:
bash
npx ngrx-devtool --server-only

Configuration Options

配置选项

DevTool Configuration Interface

DevTool配置接口

typescript
interface NgRxDevToolConfig {
  // WebSocket server URL (default: 'ws://localhost:4000')
  wsUrl?: string;
  
  // Enable/disable the devtool (default: true in development)
  enabled?: boolean;
  
  // Track NgRx effects (default: true)
  trackEffects?: boolean;
  
  // Maximum number of actions to store (default: 100)
  maxActions?: number;
  
  // Automatically connect on initialization (default: true)
  autoConnect?: boolean;
  
  // Reconnection attempts (default: 3)
  reconnectAttempts?: number;
  
  // Reconnection delay in ms (default: 1000)
  reconnectDelay?: number;
}
typescript
interface NgRxDevToolConfig {
  // WebSocket服务器URL(默认值:'ws://localhost:4000')
  wsUrl?: string;
  
  // 启用/禁用开发工具(默认:开发环境下为true)
  enabled?: boolean;
  
  // 追踪NgRx效果(默认值:true)
  trackEffects?: boolean;
  
  // 存储的最大动作数量(默认值:100)
  maxActions?: number;
  
  // 初始化时自动连接(默认值:true)
  autoConnect?: boolean;
  
  // 重连尝试次数(默认值:3)
  reconnectAttempts?: number;
  
  // 重连延迟(毫秒,默认值:1000)
  reconnectDelay?: number;
}

Environment-Based Configuration

基于环境的配置

typescript
// app.config.ts
import { isDevMode } from '@angular/core';
import { provideNgrxDevTool, createDevToolMetaReducer } from '@amadeus-it-group/ngrx-devtool';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(
      reducers,
      { 
        metaReducers: isDevMode() ? [createDevToolMetaReducer()] : []
      }
    ),
    provideNgrxDevTool({
      wsUrl: `ws://${window.location.hostname}:4000`,
      enabled: isDevMode(),
      trackEffects: true,
      maxActions: 200,
      reconnectAttempts: 5
    })
  ]
};
typescript
// app.config.ts
import { isDevMode } from '@angular/core';
import { provideNgrxDevTool, createDevToolMetaReducer } from '@amadeus-it-group/ngrx-devtool';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(
      reducers,
      { 
        metaReducers: isDevMode() ? [createDevToolMetaReducer()] : []
      }
    ),
    provideNgrxDevTool({
      wsUrl: `ws://${window.location.hostname}:4000`,
      enabled: isDevMode(),
      trackEffects: true,
      maxActions: 200,
      reconnectAttempts: 5
    })
  ]
};

Production-Safe Configuration

生产环境安全配置

typescript
// app.config.ts
import { environment } from './environments/environment';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(
      reducers,
      { 
        metaReducers: environment.devToolEnabled 
          ? [createDevToolMetaReducer()] 
          : []
      }
    ),
    provideNgrxDevTool({
      wsUrl: environment.devToolWsUrl,
      enabled: environment.devToolEnabled,
      trackEffects: environment.devToolEnabled
    })
  ]
};
typescript
// environments/environment.ts
export const environment = {
  production: false,
  devToolEnabled: true,
  devToolWsUrl: 'ws://localhost:4000'
};

// environments/environment.prod.ts
export const environment = {
  production: true,
  devToolEnabled: false,
  devToolWsUrl: ''
};
typescript
// app.config.ts
import { environment } from './environments/environment';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(
      reducers,
      { 
        metaReducers: environment.devToolEnabled 
          ? [createDevToolMetaReducer()] 
          : []
      }
    ),
    provideNgrxDevTool({
      wsUrl: environment.devToolWsUrl,
      enabled: environment.devToolEnabled,
      trackEffects: environment.devToolEnabled
    })
  ]
};
typescript
// environments/environment.ts
export const environment = {
  production: false,
  devToolEnabled: true,
  devToolWsUrl: 'ws://localhost:4000'
};

// environments/environment.prod.ts
export const environment = {
  production: true,
  devToolEnabled: false,
  devToolWsUrl: ''
};

Usage Patterns

使用模式

Monitoring Actions

监控动作

Once configured, all dispatched actions are automatically tracked:
typescript
// user.actions.ts
import { createAction, props } from '@ngrx/store';

export const loadUsers = createAction('[User List] Load Users');
export const loadUsersSuccess = createAction(
  '[User API] Load Users Success',
  props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
  '[User API] Load Users Failure',
  props<{ error: string }>()
);

// Component dispatch - automatically tracked
this.store.dispatch(loadUsers());
配置完成后,所有派发的动作都会被自动追踪:
typescript
// user.actions.ts
import { createAction, props } from '@ngrx/store';

export const loadUsers = createAction('[User List] Load Users');
export const loadUsersSuccess = createAction(
  '[User API] Load Users Success',
  props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
  '[User API] Load Users Failure',
  props<{ error: string }>()
);

// 组件派发动作 - 自动被追踪
this.store.dispatch(loadUsers());

Effect Tracking

效果追踪

Effects are automatically tracked when
trackEffects: true
:
typescript
// user.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, map, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
import * as UserActions from './user.actions';
import { UserService } from '../services/user.service';

@Injectable()
export class UserEffects {
  // This effect will be tracked by NgRx DevTool
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.loadUsers),
      switchMap(() =>
        this.userService.getUsers().pipe(
          map(users => UserActions.loadUsersSuccess({ users })),
          catchError(error => of(UserActions.loadUsersFailure({ 
            error: error.message 
          })))
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}
trackEffects: true
时,效果会被自动追踪:
typescript
// user.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, map, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
import * as UserActions from './user.actions';
import { UserService } from '../services/user.service';

@Injectable()
export class UserEffects {
  // 该效果会被NgRx DevTool追踪
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.loadUsers),
      switchMap(() =>
        this.userService.getUsers().pipe(
          map(users => UserActions.loadUsersSuccess({ users })),
          catchError(error => of(UserActions.loadUsersFailure({ 
            error: error.message 
          })))
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

State Visualization

状态可视化

The DevTool automatically captures state before and after each action, showing diffs:
typescript
// counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as CounterActions from './counter.actions';

export interface CounterState {
  count: number;
  lastUpdated: Date | null;
}

export const initialState: CounterState = {
  count: 0,
  lastUpdated: null
};

export const counterReducer = createReducer(
  initialState,
  on(CounterActions.increment, state => ({
    ...state,
    count: state.count + 1,
    lastUpdated: new Date()
  })),
  on(CounterActions.decrement, state => ({
    ...state,
    count: state.count - 1,
    lastUpdated: new Date()
  })),
  on(CounterActions.reset, () => initialState)
);
DevTool会自动捕获每个动作前后的状态,并展示差异:
typescript
// counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as CounterActions from './counter.actions';

export interface CounterState {
  count: number;
  lastUpdated: Date | null;
}

export const initialState: CounterState = {
  count: 0,
  lastUpdated: null
};

export const counterReducer = createReducer(
  initialState,
  on(CounterActions.increment, state => ({
    ...state,
    count: state.count + 1,
    lastUpdated: new Date()
  })),
  on(CounterActions.decrement, state => ({
    ...state,
    count: state.count - 1,
    lastUpdated: new Date()
  })),
  on(CounterActions.reset, () => initialState)
);

Custom Action Metadata

自定义动作元数据

Add metadata to actions for better debugging:
typescript
// product.actions.ts
import { createAction, props } from '@ngrx/store';

export const addToCart = createAction(
  '[Product] Add to Cart',
  props<{ 
    productId: string; 
    quantity: number;
    metadata?: { source: string; timestamp: number }
  }>()
);

// Dispatch with metadata
this.store.dispatch(addToCart({
  productId: 'prod-123',
  quantity: 2,
  metadata: {
    source: 'product-detail-page',
    timestamp: Date.now()
  }
}));
为动作添加元数据以优化调试体验:
typescript
// product.actions.ts
import { createAction, props } from '@ngrx/store';

export const addToCart = createAction(
  '[Product] Add to Cart',
  props<{ 
    productId: string; 
    quantity: number;
    metadata?: { source: string; timestamp: number }
  }>()
);

// 携带元数据派发动作
this.store.dispatch(addToCart({
  productId: 'prod-123',
  quantity: 2,
  metadata: {
    source: 'product-detail-page',
    timestamp: Date.now()
  }
}));

Advanced Configuration

高级配置

Selective Action Tracking

选择性动作追踪

Filter which actions to track by customizing the meta-reducer:
typescript
// app.config.ts
import { ActionReducer, Action } from '@ngrx/store';
import { createDevToolMetaReducer } from '@amadeus-it-group/ngrx-devtool';

function createFilteredDevToolMetaReducer() {
  const devToolMetaReducer = createDevToolMetaReducer();
  
  return (reducer: ActionReducer<any>) => {
    const wrappedReducer = devToolMetaReducer(reducer);
    
    return (state: any, action: Action) => {
      // Skip tracking for high-frequency actions
      if (action.type.includes('[Router]') || 
          action.type.includes('[Internal]')) {
        return reducer(state, action);
      }
      return wrappedReducer(state, action);
    };
  };
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(reducers, {
      metaReducers: [createFilteredDevToolMetaReducer()]
    }),
    provideNgrxDevTool()
  ]
};
通过自定义元 reducer 筛选需要追踪的动作:
typescript
// app.config.ts
import { ActionReducer, Action } from '@ngrx/store';
import { createDevToolMetaReducer } from '@amadeus-it-group/ngrx-devtool';

function createFilteredDevToolMetaReducer() {
  const devToolMetaReducer = createDevToolMetaReducer();
  
  return (reducer: ActionReducer<any>) => {
    const wrappedReducer = devToolMetaReducer(reducer);
    
    return (state: any, action: Action) => {
      // 跳过高频动作的追踪
      if (action.type.includes('[Router]') || 
          action.type.includes('[Internal]')) {
        return reducer(state, action);
      }
      return wrappedReducer(state, action);
    };
  };
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(reducers, {
      metaReducers: [createFilteredDevToolMetaReducer()]
    }),
    provideNgrxDevTool()
  ]
};

Multiple Environment Setup

多环境配置

typescript
// app.config.ts
const devToolConfig = (() => {
  const hostname = window.location.hostname;
  
  if (hostname === 'localhost') {
    return { wsUrl: 'ws://localhost:4000', enabled: true };
  } else if (hostname.includes('staging')) {
    return { wsUrl: 'ws://staging-devtool.example.com:4000', enabled: true };
  }
  return { enabled: false };
})();

export const appConfig: ApplicationConfig = {
  providers: [
    provideNgrxDevTool(devToolConfig)
  ]
};
typescript
// app.config.ts
const devToolConfig = (() => {
  const hostname = window.location.hostname;
  
  if (hostname === 'localhost') {
    return { wsUrl: 'ws://localhost:4000', enabled: true };
  } else if (hostname.includes('staging')) {
    return { wsUrl: 'ws://staging-devtool.example.com:4000', enabled: true };
  }
  return { enabled: false };
})();

export const appConfig: ApplicationConfig = {
  providers: [
    provideNgrxDevTool(devToolConfig)
  ]
};

Troubleshooting

故障排除

Connection Issues

连接问题

Problem: DevTool UI shows "Disconnected" status
Solutions:
  1. Verify the DevTool server is running:
bash
npx ngrx-devtool
  1. Check WebSocket URL matches server configuration:
typescript
provideNgrxDevTool({
  wsUrl: 'ws://localhost:4000' // Must match server port
})
  1. Check browser console for connection errors:
WebSocket connection to 'ws://localhost:4000' failed: Connection refused
  1. Verify no firewall blocking WebSocket connections
问题:DevTool UI显示"已断开连接"状态
解决方案
  1. 确认DevTool服务器正在运行:
bash
npx ngrx-devtool
  1. 检查WebSocket URL与服务器配置匹配:
typescript
provideNgrxDevTool({
  wsUrl: 'ws://localhost:4000' // 必须与服务器端口匹配
})
  1. 检查浏览器控制台的连接错误:
WebSocket connection to 'ws://localhost:4000' failed: Connection refused
  1. 确认没有防火墙阻止WebSocket连接

Actions Not Appearing

动作未显示

Problem: Actions are dispatched but not showing in DevTool
Solutions:
  1. Ensure meta-reducer is registered:
typescript
provideStore(reducers, {
  metaReducers: [createDevToolMetaReducer()] // Must be present
})
  1. Verify DevTool is enabled:
typescript
provideNgrxDevTool({
  enabled: true // Check this is true
})
  1. Check for action filtering that might be excluding actions
问题:动作已派发但未在DevTool中显示
解决方案
  1. 确保已注册元 reducer:
typescript
provideStore(reducers, {
  metaReducers: [createDevToolMetaReducer()] // 必须存在
})
  1. 确认DevTool已启用:
typescript
provideNgrxDevTool({
  enabled: true // 检查该值是否为true
})
  1. 检查是否存在动作筛选规则导致动作被排除

Effects Not Tracked

效果未被追踪

Problem: Effects execute but don't appear in DevTool
Solutions:
  1. Enable effect tracking:
typescript
provideNgrxDevTool({
  trackEffects: true // Must be true
})
  1. Ensure effects are properly registered:
typescript
provideEffects([UserEffects, ProductEffects])
  1. Verify effects use the
    createEffect()
    function
问题:效果已执行但未在DevTool中显示
解决方案
  1. 启用效果追踪:
typescript
provideNgrxDevTool({
  trackEffects: true // 必须为true
})
  1. 确保效果已正确注册:
typescript
provideEffects([UserEffects, ProductEffects])
  1. 确认效果使用了
    createEffect()
    函数

Performance Issues

性能问题

Problem: Application slows down with DevTool enabled
Solutions:
  1. Reduce action history limit:
typescript
provideNgrxDevTool({
  maxActions: 50 // Default is 100
})
  1. Filter high-frequency actions:
typescript
// Use custom meta-reducer to skip router/polling actions
  1. Disable in production builds:
typescript
provideNgrxDevTool({
  enabled: !environment.production
})
问题:启用DevTool后应用运行变慢
解决方案
  1. 减少动作历史记录上限:
typescript
provideNgrxDevTool({
  maxActions: 50 // 默认值为100
})
  1. 筛选高频动作:
typescript
// 使用自定义元 reducer 跳过路由/轮询动作
  1. 在生产构建中禁用:
typescript
provideNgrxDevTool({
  enabled: !environment.production
})

Port Conflicts

端口冲突

Problem: Port 4000 or 3000 already in use
Solutions:
  1. Use custom ports:
bash
npx ngrx-devtool --ws-port 5000 --ui-port 8080
  1. Update configuration to match:
typescript
provideNgrxDevTool({
  wsUrl: 'ws://localhost:5000'
})
问题:端口4000或3000已被占用
解决方案
  1. 使用自定义端口:
bash
npx ngrx-devtool --ws-port 5000 --ui-port 8080
  1. 更新配置以匹配:
typescript
provideNgrxDevTool({
  wsUrl: 'ws://localhost:5000'
})

State Diff Not Showing

状态差异未显示

Problem: State changes occur but diff viewer is empty
Solutions:
  1. Ensure reducer returns new state reference:
typescript
// ❌ Bad - mutates state
on(updateUser, (state, { user }) => {
  state.user = user; // Don't do this
  return state;
})

// ✅ Good - returns new state
on(updateUser, (state, { user }) => ({
  ...state,
  user
}))
  1. Check state serialization for circular references
问题:状态已变更但差异查看器为空
解决方案
  1. 确保reducer返回新的状态引用:
typescript
// ❌ 错误 - 直接修改状态
on(updateUser, (state, { user }) => {
  state.user = user; // 请勿这样做
  return state;
})

// ✅ 正确 - 返回新状态
on(updateUser, (state, { user }) => ({
  ...state,
  user
}))
  1. 检查状态序列化是否存在循环引用

Browser Compatibility

浏览器兼容性

The DevTool requires WebSocket support. All modern browsers support WebSockets, but if you encounter issues:
  1. Ensure browser is up to date
  2. Check corporate proxy/firewall settings
  3. Try different browser to isolate issue
  4. Check browser console for specific WebSocket errors
DevTool需要WebSocket支持。所有现代浏览器均支持WebSocket,但如果遇到问题:
  1. 确保浏览器已更新至最新版本
  2. 检查企业代理/防火墙设置
  3. 尝试使用其他浏览器以排查问题
  4. 检查浏览器控制台中的具体WebSocket错误