Reputation: 847
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
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
Reputation: 21
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
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
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
Reputation: 23078
I managed to provide parameters to the selector with a slight change on how I use it. Example:
export const selectReferentialsState = (state: AppState) => state.referentials;
export const referentialDataSelector = createSelector(
selectReferentialsState,
(state: ReferentialsState, props: { refType: Referential}) => state.data[props.refType]
);
this.availableRoles$ = this.store.select(state => referentialDataSelector(state, { refType: Referential.Role}));
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
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
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
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