Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀[FEATURE]: Provide helper selector functions for picking state properties #1672

Closed
rbudnar opened this issue Sep 15, 2020 · 2 comments
Closed

Comments

@rbudnar
Copy link

rbudnar commented Sep 15, 2020

Relevant Package

This feature request is for @ngxs/store

Description

I would like to be able to select arbitrary slices of state efficiently without having to manually create lots of individual selectors.

Describe the problem you are trying to solve

Please describe the problem. If possible please substantiate it with the use cases you want to address.

It is common to need to select a handful of properties (and use them directly) from a state store in one selector. For example, I have an AppSettings model that contains various properties used throughout my app. My initial naive implementation of such a selector looked like this:

// app settings model
export class AppSettings {
    showEstimated: boolean;
    useAllTimeDateRange: boolean;
    showRecorded: boolean;
    useBest: boolean;
    startDate: Moment | null;
    endDate: Moment | null;
}

// selectors.ts
const prConfig = createSelector([AppSettingState], (settings: AppSettings) => {
    return {
        startDate: settings.startDate,
        sndDate: settings.endDate,
        showRecorded: settings.showRecorded,
        useBest: settings.useBest,
    };
});

The problem with this implementation was that, if any property in AppSettings changes, regardless of whether or not it is listed the above selector, all consumers of this selector will recalculate unless they are guarded (e.g., with a distinctUntilChanged((x,y) => ...). I don't believe this is the right solution.

Currently to solve this problem, it appears that individual selectors are needed, which can then be aggregated into a composite selector. I broke apart my settings into different selectors as follows, which solves the problem of downstream recalculations:

const showRecorded = createSelector([AppSettingState], ({ showRecorded }: AppSettings) => showRecorded);
const useBest = createSelector([AppSettingState], ({ useBest }: AppSettings) => useBest);
const startDate = createSelector([AppSettingState], ({ startDate }: AppSettings) => startDate);
const endDate = createSelector([AppSettingState], ({ endDate }: AppSettings) => endDate);

export const prConfig = createSelector(
    [showRecorded, useBest, startDate, endDate],
    (showRecorded, useBest, startDate, endDate) => {
        return {
            startDate,
            endDate,
            showRecorded,
            useBest,
        };
    }
);

The above works, but feels overly verbose, especially if you have multiple configurations you'd like across the app for various state slices. I would love to have a simpler alternative that can pick out various arbitrary properties from a state store as needed.

See this post/thread on the NGXS slack for the discussion sparking this post.

Describe the solution you'd like

If you have a solution in mind, please describe it.

I would like a createSelector helper method of some kind that can provide a strongly typed solution to the above. I have come up with the following implementation in my own app and it currently appears to work well; I've been able to remove quite a few "boilerplate" selectors so far with this:

// utils.ts
export const createPropsSelector = <TStateModel, K extends keyof TStateModel>(
    stateToken: StateToken<TStateModel>,
    ...keys: K[]
) => {
    const selectors = keys.map(key => createSelector([stateToken], (state: TStateModel) => state[key]));
    return createSelector([...selectors], (...selected) => {
        return keys.reduce((acc, key, i) => {
            acc[key] = selected[i];
            return acc;
        }, {} as Pick<TStateModel, K>);
    });
};

// state-tokens.ts
export const APP_SETTINGS_STATE_TOKEN = new StateToken<AppSettings>("appsettings");

// sample usage
const selector = createPropsSelector(
                    APP_SETTINGS_STATE_TOKEN,
                    "useBest",
                    "startDate",
                    "endDate",
                    "showRecorded")
this.store.select(selector).pipe(...)

The above implementation both provides strong typing for consumers and solves the problem of recalculating unnecessarily when unrelated state changes occur. I hadn't personally had a reason to move over to using StateTokens until this and so I'm not sure of how well adopted they are - which could be a potential drawback for this solution - but it was trivial to add one and well worth it for the result.

Describe alternatives you've considered

Have you considered any alternative solutions or workarounds?

I'm using what I posted above in the suggested solution. I would be happy to put in a PR to add this to the library if desired. And by the way, thanks for the great library!

Edit - seems like this may be related to #1653

@arturovt
Copy link
Member

arturovt commented Aug 1, 2022

Closing as duplicate of #1653.

@markwhitfeld
Copy link
Member

markwhitfeld commented Mar 29, 2023

Great news! v3.8.0 has been released and it includes a fix for this issue.
We are closing this issue, but please re-open it if the issue is not resolved.
Please leave a comment in the v3.8.0 release discussion if you come across any regressions with respect to your issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants