Store: Add "combineSelectors" function to provide more intuitive way to combine states #3098
Replies: 7 comments
-
I like the name of |
Beta Was this translation helpful? Give feedback.
-
I think that's a good idea |
Beta Was this translation helpful? Give feedback.
-
Like 👍 I've seen many developers struggling with the creation of non-trivial selectors and "polluting" their components with rxjs compositions instead. Is this something where I can help? |
Beta Was this translation helpful? Give feedback.
-
im looking for a way to combine state from both stores (@ngrx/store and @ngrx/component which holds entities) imagine a compoent where a user does some local configuration based on global stored values (e.g. prizing, ingredients) which would be selectMix$ = createSelector(
// from component store
componentStore.selectConfigurationIds,
componentStore.selectConfigurationEntities,
// from global store
globalStore.selectPrizingEntities,
globalStore.selectIngredientsEntities,
(configIds, configEntities, prizingEntities, ingredientEntities) =>
(configIds as number[]).map(
(id) =>
({
id: id,
foo: this.fooBarService.find(
(fooBars) => fooBars.id === configEntities[id]?.foo,
),
prizing: prizingEntities[id],
ingredients: ingredientEntities[id],
} as Mix),
),
); well, this does not work as component state is independant of global state => you can not select from both stores at the same time (without additional programming effort) now i have to figure out a way to do that with rxjs ... in my opinion the proposal of "combineSelectors" would fit here edit: solution to the combination problem => mixed$ = this.componentStore.select(selectAllConfigurations).pipe(
concatLatestFrom(() => [
this.globalStore.select(selectPrizingEntities),
this.globalStore.select(selectIngredientEntities),
]),
map(
([configurations, prizings, ingredients]) =>
configurations.map(
(c) =>
({
id: c.id,
foo: this.fooBarDict[c.id],
prizings: prizings[c.id],
ingredients: ingredients[c.id],
} as Mix),
) as Mix[],
),
); |
Beta Was this translation helpful? Give feedback.
-
@steflen you can also combine the two sources in ComponentStore with selectMix$ = this.select(
// from component store
componentStore.selectConfigurationIds,
componentStore.selectConfigurationEntities,
// from global store
globalStore.select(selectPrizingEntities), // uses Store.select()
globalStore.select(selectIngredientsEntities), // uses Store.select()
(configIds, configEntities, prizingEntities, ingredientEntities) =>
(configIds as number[]).map(
(id) =>
({
id: id,
foo: this.fooBarService.find(
(fooBars) => fooBars.id === configEntities[id]?.foo,
),
prizing: prizingEntities[id],
ingredients: ingredientEntities[id],
} as Mix),
),
); |
Beta Was this translation helpful? Give feedback.
-
Just like developers may not realize the composable power of selectors with When I hear the name, for example, I think of a function that combines two pieces of data in the simplest way possible: const selectCarsAndTrains = combineSelectors(
selectCars,
selectTrains,
(cars, trains) => ({ cars, trains })
); I like the idea of using the API as a means to communicate more effectively than docs which may go unread. Selectors at their core are composable projections of state, and finding a way to communicate that clearly would unlock so much power for devs. Although they're elegant, variadic argument lists can be confusing for less experienced devs. I think If the API looked like this, however, it would be very explicit that selectors are composed of other selectors: const selectDerivedThing = createSelector({
selectors: [
selectSomething,
selectSomethingElse,
selectAnotherThing
],
projector: (something, somethingElse, anotherThing) => /* ... */
}); It's less beautiful, but more explicit in meaning. I don't have to read the signature of |
Beta Was this translation helpful? Give feedback.
-
What if we killed two birds with one stone by clarifying the const selectLabeledNumber = createSelector({
slices: [selectSomeNumber, selectSomeLabel],
projector: (aNumber, label) => `${label}: ${aNumber}`
});
selectLabeledNumber.projector; // typed as (number, string) => string Then developers will understand that selectors are composed of slices of state that are projected into a new value just from reading the API. And the transition to strict projectors will be natural with the progression of using a new selector API. |
Beta Was this translation helpful? Give feedback.
-
Creating selectors is done in two primary ways:
And doing the same with another slice of state
From seeing various trends through code reviews, its not very apparent that
createSelector
can be used to create selectors, and combine selectors. Developers usually end up combining these states using observables, which can be expensive.This change proposes an alias to
createSelector
namedcombineSelectors
.It's not a different API functionally, but something similar to
createReducer
andcombineReducers
, where we have a defined API to create vs combine selectors. This would encourage developers to combine states together in a standardized way.Other information:
If accepted, I would be willing to submit a PR for this feature
[ ] Yes (Assistance is provided if you need help submitting a pull request)
[ ] No
Beta Was this translation helpful? Give feedback.
All reactions