user3429127
user3429127

Reputation: 847

ngrx: how to pass parameters to selector inside createSelector method

I have a very simple state in my store:

const state = {
 records: [1,2,3],
};

I have a selector for records:

export const getRecords = createSelector(getState, (state: State) => state.records));

And what I want now is to have separate selectors for fetching each record by index. For this purpose I want to create one generic selector with props in this way:

export const getRecordByIndex = createSelector(
getRecords,
(state: State, { index }) => state.records[index]),
);

And after that create a couple of specific selectors e. g.:

export const getFirstRecord = createSelector(
getRecordByIndex(/* somehow pass index = 0 to this selector */),
(firstRecord) => firstRecord),
);

But I didn't find any mention how to pass parameters to selectors with props when we use them inside createSelector method. Is it possible?

Upvotes: 44

Views: 39161

Answers (8)

Rapido
Rapido

Reputation: 372

There is a better alternative proposed by @timdeschryver here:

export const hasRight = (rightCode: RightCode) => createSelector(selectUser, (user) => {
    return user?.rights?.indexOf(rightCode) !== -1;
});

// you can consume it as
const canEditClient$ = this.store.select(hasRight(RIGHT_EDIT_CLIENT));

Upvotes: 1

You can just pass the index as a parameter.

In selector file

export const getRecordByIndex = (index)=> createSelector(
getRecords,
(records) => records[index])
);

In component

this.store.select(getRecordByIndex(0)).subscribe(res=> {  //pass your index
    res; // your output 
});

you can check this blog https://timdeschryver.dev/blog/parameterized-selectors

Upvotes: 2

Mike Dalrymple
Mike Dalrymple

Reputation: 1111

This can be done with "factory selectors" as described by @timdeschryver in his comment regarding the deprecation of props from ngrx selectors.

Using the OP's request, I would have the following implementation:

export const getRecordByIndex = (index: number) => createSelector(
   getRecords, (records) => records[index]
);

That could then be used as:

const record$ = this.store.select(getRecordByIndex(1));

This seems to be the preferred way now that "Using selectors with props" is marked as Deprecated in the ngrx documentation.

Upvotes: 12

DeborahK
DeborahK

Reputation: 60518

From this blog post: https://timdeschryver.dev/blog/parameterized-selectors

As of NgRx 6.1 selectors also accepts an extra props argument. Which means you can now define a selector as the following:

export const getCount = createSelector(
  getCounterValue, 
  (counter, props) => counter * props.multiply
);

this.counter = this.store.pipe(
  select(fromRoot.getCount, { multiply: 2 })
);

Ah ... but rereading your question, you are asking then how to build another selector that uses this selector? The above-linked article suggests building a factory function.

Upvotes: 28

Alexei - check Codidact
Alexei - check Codidact

Reputation: 23078

I managed to provide parameters to the selector with a slight change on how I use it. Example:

selector (I do not use a separate feature here)

export const selectReferentialsState = (state: AppState) => state.referentials;

export const referentialDataSelector = createSelector(
    selectReferentialsState,
    (state: ReferentialsState, props: { refType: Referential}) => state.data[props.refType]
);

usage

this.availableRoles$ = this.store.select(state => referentialDataSelector(state, { refType: Referential.Role}));

Advanced usage (cascade selectors with parameters)

I will provide another example to cover the more complex scenario of having to define a selector that relies on a selector that requires parameters (props). This also includes an easier usage syntax (pipe + select):

export const selectQuestionnaireTranslationInfo = createSelector(
    selectQuestionnaireTranslationState,
    (state: QuestionnaireTranslationState, props: { formId: number}) => state.entities[props.formId]
);

export const selectQuestionnaireLanguageProgress = createSelector(
    selectQuestionnaireTranslationInfo,
    (state: QuestionnaireTemplateTranslationFullInfo, props: {formId: number, langId: number }) =>
        state?.languageInfo?.find(li => li.spTranslationLanguageId === props.langId)
);

export const selectQuestionnaireLanguageProgressCount = createSelector(
    selectQuestionnaireLanguageProgress,
    (state: QuestionnaireTemplateTranslationLanguageInfo) =>
        state?.translatedResourceCount
);

Usage:

const props = { formId: this.templateId, langId: this.languageId};
this.progressCount$ = this.store.pipe(select(selectQuestionnaireLanguageProgressCount, props));`

As already noted by Ian Jamieson, props are merged and available in the selectors chain (that's why the last selector does not require to explicitly declare the props, they are "inherited").

Upvotes: 8

user3550446
user3550446

Reputation: 455

use mapSelectors from this library reselect-mapper

see the example bellow

import { mapSelectors } from "reselect-mapper"

const getUserById = (state: State, id: string) => state.users[id]
const getPostByIndex = (state: State, index: number) => state.posts[index]

// now you can combine your selectors using mapSelectors
const getUserAndPost = mapSelectors({
         user: getUserById,
         post: getPostByIndex
      }, map => map); // map signature is { user: User, post: Post }
// getUserAndPost selector has the following signature
// (state: State, params: { user: string, post: number }) => ({ user: User, post: Post })

Upvotes: 0

Ian Jamieson
Ian Jamieson

Reputation: 4816

I am using "@ngrx/entity": "7.2.0", and I can see that props are passed to each selector, for example in my component I am calling:

this.isActive$ = this.store.pipe(select(fromClient.isActive, { id: 'someid' }));

And then in my reducer I have the following:

export const getState = createFeatureSelector('state');

export const getEntity = createSelector(
  getState,
  (state, props) => {
    // do something with props.id to get an entity then:
    return state;
  }
);

export const isActive: = createSelector(
  getEntity, // props are passed to here
  (state: any) => { // i don't add the props argument here, as i don't need them
    return state.isActive;
  }
);

Upvotes: 12

grahamaj
grahamaj

Reputation: 1664

You could use the projector function:

export interface Record {
  // Some sort of record interface
}

export interface State {
  records: Record[];
}

export const getRecords = createSelector(
  getState,
  (state: State): Record[] => state.records)
);

export const getRecordByIndex = createSelector(
  getRecords,
  (records: Record[], { index }) => records[index]),
);

export const getFirstRecord = createSelector(
  getRecords,
  (records: Record[]) => getRecordByIndex.projector(records, { index: 0 })
);

Upvotes: 17

Related Questions