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

Angular 17中Ngrx Selectors返回相同数据集问题求助

问题原因

1. 同一份Reducer被用于两个Feature,且Action无Feature区分

你为selectedPhotos和XboxPhotos两个Feature都注册了同一个photoReducer,而这个Reducer会响应全局的photosFetchAPISuccess Action。当你dispatch这个Action时,所有使用该Reducer的Feature都会执行状态更新,导致两个Feature的状态被同时覆盖。

2. 组件中错误注入多个Store实例

NgRx的Store是单例服务,不需要为不同Feature注入多个Store实例。你注入的store、filterStore、xboxStore其实都是同一个全局Store,这样的写法没有意义,反而容易混淆。


修复步骤

步骤1:为每个Feature创建独立的Action和Reducer

需要给两个Feature分别定义专属的Action和Reducer,确保状态更新相互隔离:

针对selectedPhotos的Action:

// selected-photos.actions.ts
import { createAction, props } from '@ngrx/store';
import { ProductResponse, filterValues } from './your-types';

export const invokeSelectedPhotosAPI = createAction(
  '[Selected Photos API] Invoke Photo Fetch API', 
  props<{ filter: filterValues }>()
);

export const selectedPhotosFetchAPISuccess = createAction(
  '[Selected Photos API] Fetch Success', 
  props<{ results: ProductResponse['results'], item_Count: ProductResponse['item_Count'] }>()
);

针对XboxPhotos的Action:

// xbox-photos.actions.ts
import { createAction, props } from '@ngrx/store';
import { ProductResponse, filterValues } from './your-types';

export const invokeXboxPhotosAPI = createAction(
  '[Xbox Photos API] Invoke Photo Fetch API', 
  props<{ filter: filterValues }>()
);

export const xboxPhotosFetchAPISuccess = createAction(
  '[Xbox Photos API] Fetch Success', 
  props<{ results: ProductResponse['results'], item_Count: ProductResponse['item_Count'] }>()
);

针对selectedPhotos的Reducer:

// selected-photos.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { ProductResponse } from './your-types';
import { selectedPhotosFetchAPISuccess } from './selected-photos.actions';

export const initialSelectedPhotoState: ProductResponse = { results: [], item_Count: 0 };

export const selectedPhotosReducer = createReducer(
  initialSelectedPhotoState,
  on(selectedPhotosFetchAPISuccess, (state, { results, item_Count }) => {
    return { ...state, results, item_Count };
  })
);

针对XboxPhotos的Reducer:

// xbox-photos.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { ProductResponse } from './your-types';
import { xboxPhotosFetchAPISuccess } from './xbox-photos.actions';

export const initialXboxPhotoState: ProductResponse = { results: [], item_Count: 0 };

export const xboxPhotosReducer = createReducer(
  initialXboxPhotoState,
  on(xboxPhotosFetchAPISuccess, (state, { results, item_Count }) => {
    return { ...state, results, item_Count };
  })
);

步骤2:更新AppModule中的Feature注册

使用各自独立的Reducer:

// app.module.ts
import { StoreModule } from '@ngrx/store';
import { selectedPhotosReducer } from './selected-photos.reducer';
import { xboxPhotosReducer } from './xbox-photos.reducer';

@NgModule({
  imports: [
    // ...其他模块
    StoreModule.forFeature('selectedPhotos', selectedPhotosReducer),
    StoreModule.forFeature('XboxPhotos', xboxPhotosReducer),
  ]
})
export class AppModule { }

步骤3:更新Effect,分别处理两个Action

修改Effect,监听两个不同的Action,分别触发对应的成功Action:

// photos.effects.ts
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { mergeMap, map } from 'rxjs/operators';
import { PhotosService } from './photos.service';
import { invokeSelectedPhotosAPI, selectedPhotosFetchAPISuccess } from './selected-photos.actions';
import { invokeXboxPhotosAPI, xboxPhotosFetchAPISuccess } from './xbox-photos.actions';

@Injectable()
export class PhotosEffects {
  loadSelectedPhotos$ = createEffect(() =>
    this.actions$.pipe(
      ofType(invokeSelectedPhotosAPI),
      mergeMap((prod) =>
        this.photosService.getPhotos(prod.filter).pipe(
          map((data) => selectedPhotosFetchAPISuccess({ 
            results: data.results, 
            item_Count: data.item_Count 
          }))
        )
      )
    )
  );

  loadXboxPhotos$ = createEffect(() =>
    this.actions$.pipe(
      ofType(invokeXboxPhotosAPI),
      mergeMap((prod) =>
        this.photosService.getPhotos(prod.filter).pipe(
          map((data) => xboxPhotosFetchAPISuccess({ 
            results: data.results, 
            item_Count: data.item_Count 
          }))
        )
      )
    )
  );

  constructor(private actions$: Actions, private photosService: PhotosService) {}
}

步骤4:更新Selector(保持原有逻辑即可)

// photos.selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { ProductResponse } from './your-types';

export const getPhotoState = createFeatureSelector<ProductResponse>('selectedPhotos');
export const selectPhotos = createSelector(getPhotoState, (state) => state.results);

export const getXboxPhotoState = createFeatureSelector<ProductResponse>('XboxPhotos');
export const XboxPhotos = createSelector(getXboxPhotoState, (state) => state.results);

步骤5:修改组件中的Store注入和dispatch逻辑

只需要注入一个Store实例,然后dispatch对应的Action:

// your-component.component.ts
import { Store } from '@ngrx/store';
import { invokeSelectedPhotosAPI } from './selected-photos.actions';
import { invokeXboxPhotosAPI } from './xbox-photos.actions';
import { selectPhotos, XboxPhotos } from './photos.selectors';

@Component({ /* ... */ })
export class YourComponent {
  photos$ = this.store.pipe(select(selectPhotos));
  xboxPlatform$ = this.store.pipe(select(XboxPhotos));

  constructor(private store: Store) {}

  ngOnInit() {
    // 获取全量数据
    this.store.dispatch(invokeSelectedPhotosAPI({ filter: this.filterValues }));
    
    // 获取Xbox过滤数据
    const xBoxFilter = { /* 你的过滤条件 */ };
    this.store.dispatch(invokeXboxPhotosAPI({ filter: xBoxFilter }));
  }
}

核心逻辑说明

通过为每个Feature创建专属的Action和Reducer,确保当某个Action被dispatch时,只有对应的Reducer会响应并更新自身Feature的状态,从而实现两个Feature Store的完全隔离。同时,NgRx的Store是单例,不需要注入多个实例,所有Feature的状态都在同一个Store的不同切片中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 13:18:21