40

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?

user3429127
  • 809
  • 1
  • 8
  • 13

8 Answers8

28

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.

Tuo Wang
  • 82
  • 6
DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • 6
    I feel so energetic because you shared a post of mine! :) To answer the question: the props property drills down to other selectors inside the selector. In other words `getRecordByIndex`, also retrieves the `props` property as its last argument. – timdeschryver Nov 30 '18 at 19:14
  • what does "building a factory function" exactly mean? @timdeschryver how does this help to pass a parameter to the selector (which may be determined by the selector implementation)? – JFFIGK Feb 26 '19 at 15:13
  • 1
    @JFFIGK search on "Static parameter" in the above blog post. It has an example. – timdeschryver Feb 26 '19 at 17:03
  • This may help: https://www.sitepoint.com/factory-functions-javascript/ (But OMG it has a bunch of popups!) Or this: https://stackoverflow.com/questions/8698726/constructor-function-vs-factory-functions – DeborahK Feb 26 '19 at 17:49
  • 1
    The updated url for the blog post: https://medium.com/angular-in-depth/ngrx-parameterized-selector-e3f610529f8 – Jeffrey Drake May 13 '20 at 21:01
  • 2
    The blog post doesn't exist anymore on medium. You can find it here: https://timdeschryver.dev/blog/parameterized-selectors – Carrm Dec 08 '20 at 10:27
  • @DeborahK I have selectors defined at feature level that are then "exposed" globally in a "shared.ts". I'm trying to create selectors with props but have an issue that I'm not sure how to get around with my current setup: https://gist.github.com/mindscratch/22d507f8401dc9f78627db8c61b3f931 The docs show the component using the props selector directly, which is a bit different from how I have things setup. – codecraig Jan 08 '21 at 16:04
  • 3
    props have been deprecated, I found the following comment by @timdeschryver to be very helpful in converting to factory selectors: https://github.com/ngrx/platform/issues/2980#issuecomment-819551245 – Mike Dalrymple Jun 03 '21 at 16:22
  • Selectors with props will be deprecated in ngrx v12. https://ngrx.io/guide/migration/v12 – ms007 Jun 29 '21 at 08:41
16

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 })
);
grahamaj
  • 1,334
  • 1
  • 10
  • 7
  • 3
    This is the only answer to the actual question, all the others miss the point. The downside of this solution is that `getFirstRecord` has to know the input selector `getRecords` of `getRecordByIndex` in order to pass the state on to the projector function. The implementation of `getRecordByIndex` is leaking into `getFirstRecord`. – Michael Spranger Nov 09 '20 at 16:32
12

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;
  }
);
Ian Jamieson
  • 4,376
  • 2
  • 35
  • 55
10

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.

Mike Dalrymple
  • 993
  • 12
  • 23
  • But what if `index` parameter is a variable (e.g., user text input value), the factory selector would not be updated with the new `index` value? – fujy Jul 22 '22 at 14:13
8

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").

Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
2

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

0

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 })
user3550446
  • 405
  • 3
  • 10
0

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));
Rapido
  • 301
  • 4
  • 12