Search code examples
angularngrxngrx-store

Seeking help in understanding ngrx "selectors with props"


I am trying to understand ngrx "selectors with props" [https://ngrx.io/guide/store/selectors#using-selectors-with-props]. There are two parts in the given links. The first part is clear to me and I could use it in my code. I could not understand the second part -

Keep in mind that a selector only keeps the previous input arguments in its cache. If you re-use this selector with another multiply factor, the selector would always have to re-evaluate its value. This is because it's receiving both of the multiply factors (e.g. one time 2, the other time 4). In order to correctly memoize the selector, wrap the selector inside a factory function to create different instances of the selector.

The following is an example of using multiple counters differentiated by id.

export const getCount = () =>
  createSelector(
    (state, props) => state.counter[props.id],
    (counter, props) => counter * props.multiply
  );

ngOnInit() {
  this.counter2 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter2', multiply: 2 }));
  this.counter4 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter4', multiply: 4 }));
  this.counter6 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter6', multiply: 6 }));
}

In above code, (state, props) => state.counter[props.id], part I could not understand. Can somebody help in understanding (state, props) => state.counter[props.id] in combination with usage in ngOnInit?

It appears to me that state.counter[props.id] will return expected if counter state has properties with name as id i.e counter2 or counter4.

The following link is also not explaining in details - https://blog.angularindepth.com/ngrx-parameterized-selector-e3f610529f8

A short example will be really helpful.


Solution

  • About the memoize cache in selector:

    It returns the last cached value if it's subsequently called with same parameters.

    export const getCount = () =>
      createSelector(
        (state, props) => state.counter[props.id],
        (counter, props) => counter * props.multiply
      );
    
    ngOnInit() {
    
    // Calculate selector params (counter2, 2)  and return value
    this.counter2 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter2', multiply: 2 })); 
    
    // Calculate selector params (counter4, 4) and return value
    this.counter4 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter4', multiply: 4 }));
    
    // Get Cached selector params (counter4, 4) and return value
    this.counter5 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter4', multiply: 4 })); 
    
    // Calculate selector params (counter6, 6) and return value
    this.counter6 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter6', multiply: 6 })); 
    
    // Calculate selector params (counter4, 4) and return value
    this.counter8 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter4', multiply: 4 }));
    }
    

    About State

    State will have shape we define in reducers that is created when AppModule is bootstrapping which will be update when Action is triggered.

    export const initialState: State = {
      home: 0,
      away: 0,
    };
    
    const scoreboardReducer = createReducer(
      initialState,
      on(ScoreboardPageActions.homeScore, state => ({ ...state, home: state.home + 1 })),
      on(ScoreboardPageActions.awayScore, state => ({ ...state, away: state.away + 1 })),
      on(ScoreboardPageActions.resetScore, state => ({ home: 0, away: 0 })),
      on(ScoreboardPageActions.setScores, (state, { game }) => ({ home: game.home, away: game.away }))
    );
    
    export function reducer(state: State | undefined, action: Action) {
      return scoreboardReducer(state, action);
    }
    

    Your assumption is right, state is passed on selector (the first parameter).

    It appears to me that state.counter[props.id] will return expected if counter state has properties with name as id i.e counter2 or counter4.