You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NgRx Effects无响应问题排查及代码规范咨询

NgRx Effects Not Triggering + Code Best Practices Review

First: Fix the Effects Not Running Issue

The root cause your Effects aren't executing is that you haven't registered them in your AppModule. NgRx Effects require EffectsModule to be imported and configured to run. Here's how to fix this first:

Step 1: Update AppModule.ts

Add EffectsModule.forRoot([CarEffects]) to your imports array:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { CarComponent } from './car/car.component';
import { StoreModule } from '@ngrx/store';
import { carReducer } from './car/car.reducer';
import { HttpClientModule } from '@angular/common/http';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
// Add this import
import { EffectsModule } from '@ngrx/effects';
// Import your Effects class
import { CarEffects } from './car/car.effects';

@NgModule({
  declarations: [
    AppComponent,
    CarComponent
  ],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ car: carReducer }),
    HttpClientModule,
    StoreDevtoolsModule.instrument({ maxAge: 5 }),
    // Register your Effects here
    EffectsModule.forRoot([CarEffects])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 2: Fix Other Breaking Issues in Your Code

Even after registering Effects, there are other bugs that will prevent your code from working correctly:

1. Mismatched AppState and Reducer Type

Your carReducer returns a Car[] array, but your AppState defines car as a single Car object. This causes type errors and will break Store functionality. Update AppState to match:

import { Car } from './../../models/car.model';

export interface AppState {
  readonly car: Car[]; // Change from Car to Car[]
}

2. Broken Effects Data Stream

In your carAdd Effect, the map operator doesn't return an Action, which breaks the Observable stream. If you don't need to dispatch an Action after the POST request, use tap instead of map (and add dispatch: false to the @Effect() decorator). Or return a success Action if you want to handle that case:

// Option 1: No success Action needed
@Effect({ dispatch: false }) // Add dispatch: false
carAdd = this.actions$.pipe(
  ofType(CarActions.ADD_CAR),
  switchMap((carData: CarActions.AddCar) => {
    console.log('true');
    return this.http.post('http://myapi.com/api', {
      brand: carData.payload.brand,
      model: carData.payload.model
    }).pipe(
      tap(resData => { // Use tap instead of map for side effects
        localStorage.setItem('test', 'asdasdasd');
      }),
      catchError(errorRes => {
        console.log(errorRes);
        const errorMessage = 'An unknown error occurred!';
        if (!errorRes.error || !errorRes.error.error) {
          return of(new CarActions.AddCarFail(errorMessage));
        }
        console.log(errorRes.error.error.message);
        return of(new CarActions.AddCarFail(errorRes.error.error.message));
      })
    );
  })
);

// Option 2: Add a success Action (better for tracking state)
// First define an ADD_CAR_SUCCESS action in your actions file, then:
@Effect()
carAdd = this.actions$.pipe(
  ofType(CarActions.ADD_CAR),
  switchMap((carData: CarActions.AddCar) => {
    console.log('true');
    return this.http.post('http://myapi.com/api', {
      brand: carData.payload.brand,
      model: carData.payload.model
    }).pipe(
      map(resData => {
        localStorage.setItem('test', 'asdasdasd');
        return new CarActions.AddCarSuccess(resData); // Return success Action
      }),
      catchError(errorRes => {
        // ... existing error handling
      })
    );
  })
);

3. Reducer Error Handling Bug

Your ADD_CAR_FAIL case returns an object instead of an array, which will corrupt your state (since initial state is an array). Update the reducer to handle errors properly (you should add an error property to your state type first):

// First update your state type to include error
export interface CarState {
  cars: Car[];
  error: string | null;
}

const initialState: CarState = {
  cars: [],
  error: null
};

export function carReducer(state = initialState, action: CarActions.Actions): CarState {
  switch (action.type) {
    case CarActions.ADD_CAR:
      return {
        ...state,
        cars: [...state.cars, action.payload],
        error: null
      };
    case CarActions.ADD_CAR_FAIL:
      return {
        ...state,
        error: action.payload
      };
    default:
      return state;
  }
}

// Then update AppState to use the new CarState
export interface AppState {
  readonly car: CarState;
}

Best Practices for Your NgRx Code

Here are some improvements to align with NgRx best practices:

  1. Separate State into Feature Modules
    As your app grows, move car-related state (reducers, actions, effects) into a feature module using StoreModule.forFeature() and EffectsModule.forFeature() instead of forRoot(). This keeps your code organized.

  2. Use Action Creators
    Instead of manually creating Action classes, use NgRx's createAction utility (available in NgRx 8+) for cleaner, more concise action definitions:

    import { createAction, props } from '@ngrx/store';
    import { Car } from './../../models/car.model';
    
    export const addCar = createAction(
      '[CAR] Add',
      props<{ payload: Car }>()
    );
    
    export const addCarFail = createAction(
      '[CAR] Fail',
      props<{ payload: string }>()
    );
    
    // Add a success action if needed
    export const addCarSuccess = createAction(
      '[CAR] Add Success',
      props<{ payload: any }>() // Replace "any" with your API response type
    );
    
  3. Use createReducer for Reducers
    The createReducer utility reduces boilerplate and makes reducers easier to read:

    import { createReducer, on } from '@ngrx/store';
    import * as CarActions from './car.actions';
    
    const initialState: CarState = {
      cars: [],
      error: null
    };
    
    export const carReducer = createReducer(
      initialState,
      on(CarActions.addCar, (state, { payload }) => ({
        ...state,
        cars: [...state.cars, payload],
        error: null
      })),
      on(CarActions.addCarFail, (state, { payload }) => ({
        ...state,
        error: payload
      }))
    );
    
  4. Strongly Type Effects
    Use typed Actions to avoid type casting in Effects. With action creators, you can use ofType(addCar) directly without casting:

    @Effect()
    carAdd = this.actions$.pipe(
      ofType(CarActions.addCar),
      switchMap(({ payload }) => { // No need to cast, payload is typed
        return this.http.post('http://myapi.com/api', payload).pipe(
          map(res => CarActions.addCarSuccess(res)),
          catchError(err => of(CarActions.addCarFail(err.message)))
        );
      })
    );
    
  5. Avoid Side Effects in Reducers
    Your current reducer is pure (good!), but make sure you never do async operations, API calls, or modify localStorage in reducers—those belong in Effects.

  6. Use Selectors
    Create typed selectors to access state in components, instead of directly accessing the Store. This makes your code more maintainable and reduces duplication:

    import { createFeatureSelector, createSelector } from '@ngrx/store';
    import { CarState } from './car.reducer';
    
    const selectCarState = createFeatureSelector<CarState>('car');
    
    export const selectCars = createSelector(
      selectCarState,
      (state) => state.cars
    );
    
    export const selectCarError = createSelector(
      selectCarState,
      (state) => state.error
    );
    

内容的提问来源于stack exchange,提问作者PawelC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 06:53:52