Search code examples
angularngrxngrx-storengrx-effectsstate-management

ngrx selector is called before the data is available


I have a parent component DashBoardComponent that calls loadLast3YearBalances action when a login happens

this.store
  .select(loggedInUserBankAccountId)
  .subscribe((loggedBankAccountId) => {
    this.store.dispatch(DashBoardActions.loadLast3YearBalances( { loggedBankAccountId }));
  });

this actions call an effect

  loadlast3YearsBlances$ = createEffect(() =>
this.actions$.pipe(
  ofType(DashBoardActions.loadLast3YearBalances),
  concatMap((action) =>
    this.graphHttpService.getYearlyBalance(action.loggedBankAccountId).pipe(
      tap((data) => {
        this.notifyService.showSuccess(
          'Last Three Year Balances Loaded',
          'Sucess'
        );
      }),
      map((balances) =>
        DashBoardActions.last3YearBalancesLoaded({ balances })
      ),
       catchError((err) => {
        this.notifyService.showError(
          'Error While Loading Last Three Year Balances',
          'Error'
        );
        return of(DashBoardActions.last3YearBalancesLoadError());
      })
    )
  )
)

);

DashBoardActions.last3YearBalancesLoaded this action tells the system that loadLast3YearBalances has completed.

but this Parent component has a child graph (child component) placed in its html.

   <app-total-balance-graph></app-total-balance-graph>

this total balance graph use a sector.

ngOnInit() {
this.last3YearBalances$ = this.store.pipe(
  select(last3YearBalancesSelector)
); 
 ....

this selector is defined like this

        import { createSelector } from '@ngrx/store';

    export const last3YearBalancesSelector = createSelector(
    state => state['dash'],
    (dash) => dash['lastThreeYearBalances']
    );

the reducers are define like this

        import { createReducer, on } from '@ngrx/store';
    import { LineGraphData } from '../../total-balance-graph/lineGraphData';
    import { DashBoardActions } from '../action-types';


    export interface DashState {
    lastThreeYearBalances: LineGraphData[];
    }

    export const initialDashState: DashState = {
        lastThreeYearBalances: null
    };

    /* export const reducers: ActionReducerMap<DashState> = {

    }; */

    export const dashReducer = createReducer(
        initialDashState,
        on(DashBoardActions.last3YearBalancesLoaded, (state, action) => {
            return {
                lastThreeYearBalances: action.balances
            };
        })
    );

the reducer sets 'lastThreeYearBalances' property on action last3YearBalancesLoaded but before this happens 'last3YearBalancesSelector' is called. and at that time 'lastThreeYearBalances' property is not set. and i get error.

what is that i am doing wrong here ?


Solution

  • The selector returns an Observable, which may not have emitted anything yet (technically it will have emitted the initial state). If, in your component, you need to be sure you're only using emissions containing data, you can filter for that in your component. A simple example that checks that the emission is truthy is to add:

    filter(data => !!data)
    

    to the Observable pipe in the consumer component. This way, only truthy values will pass through. Mix this with:

    <ng-container *ngIf="myObservable$ | async as data">
    </ng-container>
    

    around the area of the template which consumes the Observable, and you'll ensure that you don't get any errors.