Skip to content

Instantly share code, notes, and snippets.

@Carniatto
Last active January 13, 2021 10:56
Show Gist options
  • Save Carniatto/0e677ec9b43f949c07538e4c69e1f9ce to your computer and use it in GitHub Desktop.
Save Carniatto/0e677ec9b43f949c07538e4c69e1f9ce to your computer and use it in GitHub Desktop.
State helpers for selectors creation
/* These are some example usages */
import { createPropertySelectors, createMappedSelector } from "./ngxs-next";
interface AppStateModel {
firstName: string,
lastName: string
}
interface AuthStateModel {
logged: boolean,
grants: {
read: boolean,
write: boolean,
delete: boolean
}
}
// app.state.selectors.ts
export class AppStateSelectors {
/* Case1: Creating base props selectors from a state */
static props = createPropertySelectors<AppStateModel>(AppState);
@Selector([AppStateSelectors.props.firstName, AppStateSelectors.props.lastName])
welcomeMsg(fistName: string, lastName: string) {
return `Welccome ${firstName} ${lastName};
}
}
// auth.state.selectors.ts
export class AuthStateSelectors {
static props = createPropertySelectors<AuthStateModel>(AuthState);
}
/* Case2: Creating a mapped selector from selectors */
const authUser = createMappedSelector({
firstName: AppStateSelectors.props.firstName,
logged: AuthStateSelectors.props.logged
})
/* Case3: Creating a selector by pick properties */
const readWriteGrants = createPickSelector(AuthStateSelectors.props.grants, ['read', 'write'])
const adminGrants = createPickSelector(AuthStateSelectors.props.grants, ['read', 'write', 'delete'])
/* Just copy this file to your project and start experimenting */
import { createSelector, StateToken } from '@ngxs/store';
import { StateClass } from '@ngxs/store/internals';
type SelectorOnly<TModel> = StateToken<TModel> | ((...arg: any) => TModel);
type Selector<TModel> = StateClass<any> | SelectorOnly<TModel>;
export type PropertySelectors<TModel> = {
[P in keyof TModel]: (model: TModel) => TModel[P];
};
export function createPropertySelectors<TModel>(
state: Selector<TModel>,
): PropertySelectors<TModel> {
const cache: Partial<PropertySelectors<TModel>> = {};
return new Proxy(
{},
{
get(target: any, prop: string) {
const selector = cache[prop] || createSelector([state], (s: TModel) => s?.[prop]);
cache[prop] = selector;
return selector;
},
},
);
}
interface SelectorMap {
[key: string]: SelectorOnly<any>;
}
type MappedSelector<T extends SelectorMap> = (...args: any[]) => MappedResult<T>;
type MappedResult<TSelectorMap> = {
[P in keyof TSelectorMap]: TSelectorMap[P] extends SelectorOnly<infer R> ? R : never;
};
export function createMappedSelector<T extends SelectorMap>(selectorMap: T): MappedSelector<T> {
const selectors = Object.values(selectorMap);
return createSelector(selectors, (...args) => {
return Object.keys(selectorMap).reduce((obj, key, index) => {
(obj as any)[key] = args[index];
return obj;
}, {} as MappedResult<T>);
}) as MappedSelector<T>;
}
export function createPickSelector<TModel, Key extends keyof TModel>(
state: Selector<TModel>,
keys: Key[]
) {
const selectors = keys.map((key) => createSelector([state], (s: TModel) => s[key]));
return createSelector([...selectors], (...selected: any) => {
return keys.reduce((acc, key, index) => {
acc[key] = selected[index];
return acc;
}, {} as Pick<TModel, Key>);
});
}
@Carniatto
Copy link
Author

This is an initial proposal for state slicers. Please provide your feedback in the issue here

@markwhitfeld
Copy link

markwhitfeld commented Nov 5, 2020

Just a note that line 22 of ngxs-next.ts might need to be different if you are using an older version of TypeScript.
Like so:

const selector = cache[prop] || createSelector([state], (s: TModel) => s && s[prop]);

@markwhitfeld
Copy link

@Carniatto
Found a bug in the createPickSelector. It should be picking from the index in the selector arguments and not the key:

export function createPickSelector<TModel, Key extends keyof TModel>(
  state: Selector<TModel>,
  keys: Key[]
) {
  const selectors = keys.map((key) => createSelector([state], (s: TModel) => s[key]));
  return createSelector([...selectors], (...selected: any) => {
    return keys.reduce((acc, key, index) => {
      acc[key] = selected[index];
      return acc;
    }, {} as Pick<TModel, Key>);
  });
}

@Carniatto
Copy link
Author

Thanks @markwhitfeld Gist updated

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