📚 [DOCS]: Share your custom State Operators here! #1900
Replies: 14 comments
-
(As an example) Use Case:Sometimes we need to modify the state only if some condition is true. This operator will help you out here! Example:setState(
iif((state) => state.message === 'Hello',
patch<MyState>({ waving: true }),
patch<MyState>({ waving: false })
)
); The Code:Source: iif.ts |
Beta Was this translation helpful? Give feedback.
-
Hi! @markwhitfeld This is my operator proposal. Use Case: Sometimes we need to modify multiple elements of an array only if some condition is true or a array of indices. This operator will help you out here! 2-Complete only if text is 'ipsum': 3-Complete only idices 0 and 2: Source: https://github.com/mailok/todo-ngxs/blob/master/src/app/store/operators/update-many-items.ts |
Beta Was this translation helpful? Give feedback.
-
and another proposal Use Case: Sometimes we need to remove multiple elements of an array only if some condition is true or a array of indices. This operator will help you out here! Examples: 2-Remove index 1 and 2: Source: https://github.com/mailok/todo-ngxs/blob/master/src/app/store/operators/remove-many-items.ts |
Beta Was this translation helpful? Give feedback.
-
Use Case:In our app, we have a lot of state that looks like this : {
folder1: [{name: 'file1'}, {name: 'file2'}, {name: 'file3'}],
folder2: [{name: 'file5'}, {name: 'file4'}],
} And the key are dynamic and can be added at runtime. So we created an operator Example:It can be used to set the value : setState(
patchKey('folder1', [{"name": "file5"}]),
); Or to apply a state operator to the value : setState(
patchKey('folder1', updateItem(item => item.name === 'file2', patch({ isOpen: true }))),
); The Code:Source: patchKey.ts && patchKey.spec.ts |
Beta Was this translation helpful? Give feedback.
-
Use Case:When you have in your store an array where you could have duplication, like a state of selected items : {
selectedId: [1, 2, 3, 2],
} With this operator, you can filter out dupliacates. It can be use in a standalone way or with composition. Example:Filter out duplicates : setState(
patch({ selectedId: uniqArray() }),
); Or be composable : setState(
patch({
selectedId: compose(
append([3, 1]),
uniqArray(),
),
}),
); The Code: |
Beta Was this translation helpful? Give feedback.
-
Use Case:When you need to append items but skip those already in existing array. Allows to compare by any field if is object Example:
The Code:Source: append-uniq |
Beta Was this translation helpful? Give feedback.
-
Use Case:When you need to sort items before you store them. Allows to sort by any field of T Example:
The Code: |
Beta Was this translation helpful? Give feedback.
-
My team just started getting into state operators. It's a super cool concept, and I am certainly looking for more operators my team can create/adopt. We've identified two commonly used state model patterns in our application. I refer to these as Entities State OperatorsOur // helpers
export const getKey = <T>(entity: T, fn?: (e: T) => number | string) => (fn ? fn(entity) : (entity as any).id);
export function map<T>(entities: T[], getId?: (t: T) => number | string) {
return entities.reduce((acc, entity) => {
acc[getKey(entity, getId)] = entity;
return acc;
}, {});
} // operators
export interface EntitiesStateModel<T> {
map: { [id: string]: T };
}
export function patchMap<T>(entities: T[], getId?: (t: T) => number | string): StateOperator<EntitiesStateModel<T>> {
const idPatchMap = patch(map(entities, getId));
return patch<EntitiesStateModel<T>>({ map: idPatchMap });
}
export function setMap<T>(entities: T[], getId?: (t: T) => number | string): StateOperator<EntitiesStateModel<T>> {
return patch<EntitiesStateModel<T>>({
map: map(entities, getId)
});
}
export function patchEntity<T>(id: number | string, deepPartial: DeepPartial<T>): StateOperator<EntitiesStateModel<T>> {
return (state: Readonly<EntitiesStateModel<T>>) => ({
...state,
map: {
...state.map,
// we have a custom implementation to deepMerge, but it does what it sounds like: https://www.npmjs.com/package/deepmerge
[id]: deepMerge(state.map[id], deepPartial)
}
});
}
export function setEntity<T>(entity: T, id: number | string = getKey(entity)): StateOperator<EntitiesStateModel<T>> {
const idPatchMap = patch({ [id as any]: entity });
return patch<EntitiesStateModel<T>>({ map: idPatchMap });
} Usage:
Selected State OperatorsOur // operators
export interface SelectedStateModel {
ids: { [id: number]: boolean };
}
export function clear(): StateOperator<SelectedStateModel> {
return patch<SelectedStateModel>({
ids: {}
});
}
export function deselect(idsToDeselect: number[]): StateOperator<SelectedStateModel> {
return (state: Readonly<SelectedStateModel>) => {
const previous = state.ids;
const blacklist = idsToDeselect.reduce((acc, id) => {
acc[id] = id;
return acc;
}, {});
const ids = Object.keys(state.ids).reduce((acc, id) => {
if (id in blacklist) {
return acc;
}
acc[id] = previous[id];
return acc;
}, {});
return { ...state, ids };
};
}
export function toggle(idsToToggle: number[]): StateOperator<SelectedStateModel> {
return (state: Readonly<SelectedStateModel>) => {
const ids = idsToToggle.reduce(
(acc, id) => {
acc[id] = !acc[id];
return acc;
},
{ ...state.ids }
);
return { ...state, ids };
};
}
export function select(idsToSelect: number[]): StateOperator<SelectedStateModel> {
return (state: Readonly<SelectedStateModel>) => {
const ids = idsToSelect.reduce(
(acc, id) => {
acc[id] = true;
return acc;
},
{ ...state.ids }
);
return { ...state, ids };
};
} Usage
|
Beta Was this translation helpful? Give feedback.
-
Upsert ItemI want to insert or update an item of an array in the state, I can use ctx.setState(
patch<FoodModel>({
foods: iif<Food[]>(
(foods) => foods.some((f) => f.id === foodId),
updateItem<Food>((f) => f.id === foodId, food),
insertItem(food)
),
})
) To simplify this I can use ctx.setState(
patch<FoodModel>({
foods: upsertItem<Food>((f) => f.id === foodId, food),
})
) Here is the `upsertItem` operator:import { StateOperator } from '@ngxs/store';
import { Predicate } from '@ngxs/store/operators/internals';
export function upsertItem<T>(
selector: number | Predicate<T>,
upsertValue: T
): StateOperator<RepairType<T>[]> {
return function insertItemOperator(
existing: Readonly<RepairType<T>[]>
): RepairType<T>[] {
let index = -1;
if (isPredicate(selector)) {
index = existing.findIndex(selector);
} else if (isNumber(selector)) {
index = selector;
}
if (invalidIndex(index)) {
// Insert Value
// Have to check explicitly for `null` and `undefined`
// because `value` can be `0`, thus `!value` will return `true`
if (isNil(upsertValue) && existing) {
return existing as RepairType<T>[];
}
// Property may be dynamic and might not existed before
if (!Array.isArray(existing)) {
return [upsertValue as RepairType<T>];
}
const clone = existing.slice();
clone.splice(0, 0, upsertValue as RepairType<T>);
return clone;
} else {
// Update Value
// If the value hasn't been mutated
// then we just return `existing` array
if (upsertValue === existing[index]) {
return existing as RepairType<T>[];
}
const clone = existing.slice();
clone[index] = upsertValue as RepairType<T>;
return clone;
}
};
}
export function isUndefined(value: any): value is undefined {
return typeof value === 'undefined';
}
export function isPredicate<T>(
value: Predicate<T> | boolean | number
): value is Predicate<T> {
return typeof value === 'function';
}
export function isNumber(value: any): value is number {
return typeof value === 'number';
}
export function invalidIndex(index: number): boolean {
return Number.isNaN(index) || index === -1;
}
export function isNil<T>(
value: T | null | undefined
): value is null | undefined {
return value === null || isUndefined(value);
}
export type RepairType<T> = T extends true
? boolean
: T extends false
? boolean
: T; |
Beta Was this translation helpful? Give feedback.
-
@marcjulian here are some basic tests for import { patch } from '@ngxs/store/operators'; type Item = { id: string; name?: string };
|
Beta Was this translation helpful? Give feedback.
-
@marcjulian An alternative to your implementation would be to combine the existing operators to create a new operator. import { StateOperator } from '@ngxs/store';
import { Predicate } from '@ngxs/store/operators/internals';
import { compose, iif, insertItem, updateItem } from '@ngxs/store/operators';
export function upsertItem<T>(
selector: number | Predicate<T>,
upsertValue: T
): StateOperator<T[]> {
return compose<T[]>(
(foods) => <T[]>(foods || []),
iif<T[]>(
(foods) => Number(selector)=== selector,
iif<T[]>(
(foods) => selector < foods.length,
<StateOperator<T[]>>updateItem(selector, upsertValue),
<StateOperator<T[]>>insertItem(upsertValue, <number>selector)
),
iif<T[]>(
(foods) => foods.some(<any>selector),
<StateOperator<T[]>>updateItem(selector, upsertValue),
<StateOperator<T[]>>insertItem(upsertValue)
)
)
);
} What do you think? |
Beta Was this translation helpful? Give feedback.
-
Hi, I met a problem with your amazing operator @mailok . My feature should set the options of an object array. something like: And what i want to do is checked few of them by filtring by them id if you try to do updateManyItems( The first error spotted is about the invalidIndexs function. you couldn't do and indices[2] is undefined, and of course existing[undefined] doesn't exist the same probleme is present in update-many-items.ts to Thanks! |
Beta Was this translation helpful? Give feedback.
-
I'm trying to write a custom operator and have the following. I was following the logic from the existing operators and ended up using the same internal utils. I know I could write these in a different way that doesn't use any of the provided helpers..but I'd rather just use what's already available. As you might guess, I'm getting It seems like these helpers aren't supposed to be available for custom operators which seems counter intuitive (i.e. we have these examples that use these helpers, but we're not allowed to use them ourselves). I wasn't able to find a module for angular to use these. Am I just missing something or am I really not supposed to use these functions/type?
|
Beta Was this translation helpful? Give feedback.
-
Please participate in the RFC for the |
Beta Was this translation helpful? Give feedback.
-
NGXS Provides some default state operators but the real power is in the pattern it offers.
Please share your custom state operators here, someone else may find them useful.
If they are general purpose, logical and popular then they might even get added to the default operators in the lib!
RULES to keep this thread clean:
PS. What makes a good operator?
updateItem
but not forinsertItem
)PS. If you have no clue what this is about read this article.
Beta Was this translation helpful? Give feedback.
All reactions