Home > Net >  Ngrx 15 selector returning whole state instead of slice
Ngrx 15 selector returning whole state instead of slice

Time:01-29

I'm implementing ngrx in an Angular 15 project and I've encountered some behaviour where my selectors seem to be returning the entire state instead of the slice I anticipate. I've got my project setup as follows:

deals.actions.ts

import {createActionGroup, emptyProps, props} from "@ngrx/store";
import {Deal} from "../../models";

export const DealsApiActions = createActionGroup({
  source: 'Deals API',
  events: {
    'Fetch Deals': emptyProps(),
    'Set Deals': props<{ deals: Deal[] }>(),
    'Set Loading': props<{loadingDeals: boolean}>()
  }
})

deals.effects.ts

import {Injectable} from "@angular/core";
import {Actions, createEffect, ofType} from "@ngrx/effects";
import {DealsApiActions} from "../actions/deals.actions";
import {map, switchMap} from "rxjs/operators";
import {DealService} from "../../common/services/deal.service";
import {DealResponse} from "../../models";

@Injectable()
export class DealsEffects {
  constructor(private actions$: Actions,
              private dealsService: DealService) {}

  fetchDeals$ = createEffect(() =>
    this.actions$.pipe(
      ofType(DealsApiActions.fetchDeals),
      switchMap(() => this.dealsService.fetchDeals().pipe(
        map((dealResponse: DealResponse) => {
          return DealsApiActions.setDeals({deals: dealResponse.data})
        })
      ))
    )
  )
}

deals.reducers.ts

import {createReducer, on} from "@ngrx/store";
import {DealsApiActions} from "../actions/deals.actions";
import {Deal} from "../../models";

export interface DealState {
  deals: Deal[],
  loadingDeals: boolean
}

export const initialState: DealState = {
  deals: [],
  loadingDeals: false
}

export const dealsReducer = createReducer(
  initialState,
  on(DealsApiActions.fetchDeals, (state) => ({...state, loadingDeals: true})),
  on(DealsApiActions.setDeals, (state, {deals}) => ({...state, deals: deals})),
  on(DealsApiActions.setLoading, (state, {loadingDeals}) => ({
    ...state,
    loadingDeals: loadingDeals
  }))
)

deals.selectors.ts

import {DealState} from "../reducers/deals.reducer";

export const selectDeals = (state: DealState) => state.deals
export const selectLoadingDeals = (state: DealState) => state.loadingDeals

app.module.ts

// ...imports

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    IonicModule.forRoot(),
    AppRoutingModule,
    EffectsModule.forRoot([
      DealsEffects
    ]),
    StoreModule.forRoot({
      deals: dealsReducer
    }, {}),
    StoreDevtoolsModule.instrument({maxAge: 25, logOnly: !isDevMode()})
  ],
  providers: [
    {provide: RouteReuseStrategy, useClass: IonicRouteStrategy},
    Meta,
    DealService
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Dispatching my fetchDeals action from my component successfully calls the API and retrieves data, however when I try and select the deals data from the state, the object is returned as the full state.

app.component.ts

import {Component, OnInit} from '@angular/core';
import {Observable, Subscription} from "rxjs";
import {Router} from "@angular/router";
import {Filter} from "../../../models";
import {select, Store} from "@ngrx/store";
import {DealsApiActions} from "../../../store/actions/deals.actions";
// import {DealsSelectors} from "../../../store/selectors/deals.selectors";
import {DealState} from "../../../store/reducers/deals.reducer";
import * as selectors from '../../../store/selectors/deals.selectors';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

  subscriptions: Subscription[] = [];
  otherDeals$ = this.store.pipe(select(selectors.selectDeals))
  deals$ = this.store.pipe(select((state) => state.deals))

  constructor(private readonly router: Router,
              private readonly store: Store<DealState>) {
    this.store.dispatch(DealsApiActions.fetchDeals());
  }

  ngOnInit(): void {
  }
}

Instead of retrieving the data in the format I'd expect of:

[
  { id: 0, name: 'A N' },
  { id: 1, name: 'Other' }
]

Both methods of selecting is returning:

{
  "deals": [
    { id: 0, name: 'A N' },
    { id: 1, name: 'Other' }
  ],
  "loadingDeals": true
}

I know I've missed something really stupid, but I'm totally code-blind by this point.

CodePudding user response:

You need to update your selector file like below. You create your app's state and include deal: DealState as a single slice of state you can select from. Then you select that slice of state and use create selector with your previous select functions to properly select the data you are looking for.

deals.selector.ts

import { createSelector } from '@ngrx/store';
import { DealState } from "../reducers/deals.reducer";

export interface AppState {
  deal: DealState;
}

export const selectDeal = (state: AppState) => state.deal;

// This is what you import in your component
export const selectDeals = createSelector(
  selectDeal,
  (state: DealState) => state.deals
);
export const selectLoadingDeals = createSelector(
selectDeal,
(state: DealState) => state.loadingDeals

app.component.ts

deals$ = this.store.select(selectors.selectDeals)
  • Related