@rx-angular/cdk
Version:
@rx-angular/cdk is a Component Development Kit for ergonomic and highly performant angular applications. It helps to to build Large scale applications, UI libs, state management, rendering systems and much more. Furthermore the unique way of mixing reacti
720 lines (704 loc) • 20.2 kB
TypeScript
/**
* @description
* Accepts an array of objects of type T and single key or array of keys (K extends keyof T).
* The `exctract` method is pure and immutable, thus not touching the input values and returning a shallow
* copy of the extracted source.
*
* @example
*
* const cats = [{id: 1, type: 'cat', name: 'Fluffy'}, {id: 2, type: 'cat', name: 'Emma'}];
*
* const catsWithoutTypes = extract(cats, ['name', 'id']);
*
* // catsWithoutTypes will be:
* // [{id: 1, name: 'Fluffy'}, {id: 2, name: 'Emma'}];
*
* @example
* // Usage with RxState
*
* export class AnimalsListComponent {
*
* constructor(private state: RxState<ComponentState>, private api: ApiService) {
* state.connect(
* 'animals'
* this.api.getAnimals(),
* (state, animals) => extract(animals, ['id', 'name'])
* );
* }
* }
*
* @returns T
*
* @docsPage slice
* @docsCategory transformation-helpers
*/
declare function extract<T extends object, K extends keyof T>(array: T[], keys: K | K[]): Pick<T, K>[];
/**
* @description
* Inserts one or multiple items to an array T[].
* Returns a shallow copy of the updated array T[], and does not mutate the original one.
*
* @example
* // Inserting single value
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}];
*
* const updatedCreatures = insert(creatures, {id: 3, type: 'parrot'});
*
* // updatedCreatures will be:
* // [{id: 1, type: 'cat'}, {id: 2, type: 'dog}, {id: 3, type: 'parrot}];
*
* @example
* // Inserting multiple values
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}];
*
* const updatedCreatures = insert(creatures, [{id: 3, type: 'parrot'}, {id: 4, type: 'hamster'}]);
*
* // updatedCreatures will be:
* // [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}, {id: 3, type: 'parrot'}, {id: 4, type: 'hamster'}];
*
* @example
* // Usage with RxState
*
* export class ListComponent {
*
* readonly insertCreature$ = new Subject<void>();
*
* constructor(private state: RxState<ComponentState>) {
* // Reactive implementation
* state.connect(
* 'creatures',
* this.insertCreature$,
* ({ creatures }) => {
* const creatureToAdd = {id: generateId(), name: 'newCreature', type: 'dinosaur' };
* return insert(creatures, creatureToAdd);
* }
* );
* }
*
* // Imperative implementation
* insertCeature(): void {
* const creatureToAdd = {id: generateId(), name: 'newCreature', type: 'dinosaur' };
* this.state.set({ creatures: insert(this.state.get().creatures, creatureToAdd)});
* }
* }
*
*
* @returns T[]
*
* @docsPage insert
* @docsCategory transformation-helpers
*/
declare function insert<T>(source: T[], updates: T | T[]): T[];
/**
* @description
* The function which is used by `KeyCompareMap` to determine if changes are distinct or not.
* Should return true if values are equal.
*
* @param {T} oldVal
* @param {T} newVal
*
* @return boolean
*
* @docsPage interfaces
* @docsCategory operators
*/
type CompareFn<T> = (oldVal: T, newVal: T) => boolean;
type ComparableData<T> = CompareFn<T> | keyof T | (keyof T)[];
/**
* @description
* Removes one or multiple items from an array T[].
* For comparison you can provide a key, an array of keys or a custom comparison function that should return true if items match.
* If no comparison data is provided, an equality check is used by default.
* Returns a shallow copy of the updated array T[], and does not mutate the original one.
*
* @example
* // Removing value without comparison data
*
* const items = [1,2,3,4,5];
*
* const updatedItems = remove(items, [1,2,3]);
*
* // updatedItems will be: [4,5];
*
* @example
* // Removing values with comparison function
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'unicorn'}, {id: 3, type: 'kobold'}];
*
* const nonExistingCreatures = [{id: 2, type: 'unicorn'}, {id: 3, type: 'kobold'}];
*
* const realCreatures = remove(creatures, nonExistingCreatures, (a, b) => a.id === b.id);
*
* // realCreatures will be: [{id: 1, type: 'cat'}];
*
* @example
* // Removing values with key
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'unicorn'}, {id: 3, type: 'kobold'}];
*
* const nonExistingCreatures = [{id: 2, type: 'unicorn'}, {id: 3, type: 'kobold'}];
*
* const realCreatures = remove(creatures, nonExistingCreatures, 'id');
*
* // realCreatures will be: [{id: 1, type: 'cat'}];
*
* @example
* // Removing values with array of keys
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'unicorn'}, {id: 3, type: 'kobold'}];
*
* const nonExistingCreatures = [{id: 2, type: 'unicorn'}, {id: 3, type: 'kobold'}];
*
* const realCreatures = remove(creatures, nonExistingCreatures, ['id', 'type']);
*
* // realCreatures will be: [{id: 1, type: 'cat'}];
*
* @example
* // Usage with RxState
*
* export class ListComponent {
*
* readonly removeCreature$ = new Subject<Creature>();
*
* constructor(private state: RxState<ComponentState>) {
* // Reactive implementation
* state.connect(
* 'creatures',
* this.removeCreature$,
* ({ creatures }, creatureToRemove) => {
* return remove(creatures, creatureToRemove, (a, b) => a.id === b.id);
* }
* );
* }
*
* // Imperative implementation
* removeCreature(creatureToRemove: Creature): void {
* this.state.set({ creatures: remove(this.state.get().creatures, creatureToRemove, (a, b) => a.id === b.id)});
* }
* }
*
* @returns T[]
*
* @docsPage remove
* @docsCategory transformation-helpers
*/
declare function remove<T>(source: T[], scrap: Partial<T>[] | Partial<T>, compare?: ComparableData<T>): T[];
/**
* @description
* Allows to pass only keys which value is of specific type.
*
* @example
*
* interface Creature {
* id: number;
* type: string;
* name: string;
* }
*
* const cat = {id: 1, type: 'cat', name: 'Fluffy'};
*
* function updateCreature<T>(creature: T, key: OnlyKeysOfSpecificType<T, string>, value: string) {
* // update logic
* }
*
* // Valid key
* updateCreature(cat, 'name', 'Luna');
*
* // Invalid key
* updateCreature(cat, 'id', 3);
*
* @docsPage OnlyKeysOfSpecificType
* @docsCategory interfaces
*/
type OnlyKeysOfSpecificType<T, S> = {
[Key in keyof T]: S extends T[Key] ? Key : never;
}[keyof T];
/**
* @description
* Converts an array of objects to a dictionary {[key: string]: T}.
* Accepts array T[] and key of type string, number or symbol as inputs.
*
*
* @example
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}, {id: 3, type: 'parrot'}];
*
* const creaturesDictionary = toDictionary(creatures, 'id');
*
* // creaturesDictionary will be:
* // {
* // 1: {id: 1, type: 'cat'},
* // 2: {id: 2, type: 'dog'},
* // 3: {id: 3, type: 'parrot'}
* // };
* @example
* // Usage with RxState
*
* export class ListComponent {
*
* readonly convertToDictionary$ = new Subject();
*
* constructor(private state: RxState<ComponentState>) {
* // Reactive implementation
* state.connect(
* 'creaturesDictionary',
* this.convertToDictionary$,
* ({ creatures }) => {
* return toDictionary(creatures, 'id');
* }
* );
* }
*
* // Imperative implementation
* convertToDictionary(): void {
* this.state.set({ creaturesDictionary: toDictionary(this.state.get().creatures, 'id'});
* }
* }
*
* @see {@link OnlyKeysOfSpecificType}
* @param {OnlyKeysOfSpecificType<T, S>} key
* @returns { [key: string]: T[] }
* @docsPage toDictionary
* @docsCategory transformation-helpers
*/
declare function toDictionary<T extends object>(source: T[], key: OnlyKeysOfSpecificType<T, number> | OnlyKeysOfSpecificType<T, string> | OnlyKeysOfSpecificType<T, symbol>): {
[key: string]: T;
};
/**
* @description
* Updates one or multiple items in an array T[].
* For comparison you can provide key, array of keys or a custom comparison function that should return true if items match.
* If no comparison is provided, an equality check is used by default.
* Returns a shallow copy of the array T[] and updated items, does not mutate the original array.
*
* @example
* // Update with comparison function
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}];
*
* const newCat = {id: 1, type: 'lion'};
*
* const updatedCreatures = update(creatures, newCat, (a, b) => a.id === b.id);
*
* // updatedCreatures will be:
* // [{id: 1, type: 'lion'}, {id: 2, type: 'dog'}];
*
* @example
* // Update with key
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}];
*
* const newCat = {id: 1, type: 'lion'};
*
* const updatedCreatures = update(creatures, newCat, 'id');
*
* // updatedCreatures will be:
* // [{id: 1, type: 'lion'}, {id: 2, type: 'dog'}];
*
* @example
* // Update with array of keys
*
* const creatures = [{id: 1, type: 'cat', name: 'Bella'}, {id: 2, type: 'dog', name: 'Sparky'}];
*
* const newCat = {id: 1, type: 'lion', name: 'Bella'};
*
* const updatedCreatures = update(creatures, newCat, ['id', 'name']);
*
* // updatedCreatures will be:
* // [{id: 1, type: 'lion', name: 'Bella'}, {id: 2, type: 'dog', name: 'Sparky'}];
*
* @example
* // Usage with RxState
*
* export class ListComponent {
*
* readonly updateCreature$ = new Subject<Creature>();
*
* constructor(private state: RxState<ComponentState>) {
* // Reactive implementation
* state.connect(
* 'creatures',
* this.updateCreature$,
* ({ creatures }, creatureToUpdate) => {
* return update(creatures, creatureToUpdate, (a, b) => a.id === b.id);
* }
* );
* }
*
* // Imperative implementation
* updateCreature(creatureToUpdate: Creature): void {
* this.state.set({ creatures: update(this.state.get().creatures, creatureToUpdate, (a, b) => a.id === b.id)});
* }
* }
*
* @returns T[]
*
* @docsPage update
* @docsCategory transformation-helpers
*/
declare function update<T extends object>(source: T[], updates: Partial<T>[] | Partial<T>, compare?: ComparableData<T>): T[];
/**
* @description
* Updates or inserts (if does not exist) one or multiple items in an array T[].
* For comparison you can provide a key, an array of keys or a custom comparison function that should return true if
* items match.
* If no comparison is provided, an equality check is used by default.
* upsert is `pure` and `immutable`, your inputs won't be changed
*
*
* @example
* // Upsert (update) with key
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}];
*
* const newCat = {id: 1, type: 'lion'};
*
* const updatedCreatures = upsert(creatures, newCat, 'id');
*
* // updatedCreatures will be:
* // [{id: 1, type: 'lion'}, {id: 2, type: 'dog'}];
*
* @example
* // Upsert (insert) with key
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}];
*
* const newCat = {id: 3, type: 'lion'};
*
* const updatedCreatures = upsert(creatures, newCat, 'id');
*
* // updatedCreatures will be:
* // [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}, {id: 3, type: 'lion'}];
*
* @example
* // Upsert (update) with array of keys
*
* const creatures = [{id: 1, type: 'cat', name: 'Bella'}, {id: 2, type: 'dog', name: 'Sparky'}];
*
* const newCat = {id: 1, type: 'lion', name: 'Bella'};
*
* const updatedCreatures = upsert(creatures, newCat, ['id', 'name']);
*
* // updatedCreatures will be:
* // [{id: 1, type: 'lion', name: 'Bella'}, {id: 2, type: 'dog', name: 'Sparky'}];
*
* @example
* // Update (insert) with comparison function
*
* const creatures = [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}];
*
* const newCat = {id: 3, type: 'lion'};
*
* const updatedCreatures = upsert(creatures, newCat, (a, b) => a.id === b.id);
*
* // updatedCreatures will be:
* // [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}, {id: 3, type: 'lion'}];
*
* @example
* // Usage with RxState
*
* export class ListComponent {
*
* // trigger which gets called on add/update (for reactive implementation)
* readonly addOrUpdateCreature = new Subject<Creature>();
*
* constructor(private state: RxState<ComponentState>) {
* const initialCreatures = [{id: 1, type: 'cat', name: 'Bella'}, {id: 2, type: 'dog', name: 'Sparky'}];
* state.set({ creatures: initialCreatures });
* // Reactive implementation
* state.connect(
* 'creatures',
* this.addOrUpdateCreature,
* ({ creatures }, creatureToUpsert) => {
* return upsert(creatures, creatureToUpsert, 'id');
* }
* );
* }
*
* // Imperative implementation
* updateCreature(creatureToUpdate: Creature): void {
* this.state.set({ creatures: upsert(this.state.get('creatures'), creatureToUpdate, 'id')});
* }
* }
*
* @returns T[]
*
* @docsPage upsert
* @docsCategory transformation-helpers
*/
declare function upsert<T>(source: T[], update: Partial<T>[] | Partial<T>, compare?: ComparableData<T>): T[];
/**
* @description
* Accepts an object of type T and key of type K extends keyof T.
* Removes property from an object and returns a shallow copy of the updated object without specified property.
* If property not found returns copy of the original object.
* Not mutating original object.
*
* @example
*
* const cat = {id: 1, type: 'cat', name: 'Fluffy'};
*
* const anonymusCat = deleteProp(cat, 'name');
*
* // anonymusCat will be:
* // {id: 1, type: 'cat'};
*
* @example
* // Usage with RxState
*
* export class ProfileComponent {
*
* readonly removeName$ = new Subject();
*
* constructor(private state: RxState<ComponentState>) {
* // Reactive implementation
* state.connect(
* this.removeName$,
* (state) => {
* return deleteProp(state, 'name');
* }
* );
* }
*
* // Imperative implementation
* removeName(): void {
* this.state.set(remove(this.get(), 'name'));
* }
* }
*
* @returns Omit<T, K>
*
* @docsPage deleteProp
* @docsCategory transformation-helpers
*/
declare function deleteProp<T extends object, K extends keyof T>(object: T, key: K): Omit<T, K>;
/**
* @description
* Converts a dictionary of type {[key: string]: T} to array T[].
*
* @example
*
* const creaturesDictionary = {
* '1': {id: 1, type: 'cat'},
* '2': {id: 2, type: 'dog'},
* '3': {id: 3, type: 'parrot'}
* };
*
* const creaturesArray = dictionaryToArray(creaturesDictionary);
*
* // creaturesArray will be:
* // [{id: 1, type: 'cat'}, {id: 2, type: 'dog'}, {id: 3, type: 'parrot'}];
*
* @example
* // Usage with RxState
*
* export class ListComponent {
* readonly removeName$ = new Subject();
*
* constructor(
* private state: RxState<ComponentState>,
* private api: ApiService
* ) {
* // Reactive implementation
* state.connect(
* 'creatures',
* this.api.creaturesDictionary$,
* (_, creatures) => {
* return dictionaryToArray(creatures);
* }
* );
* }
*
* // Imperative implementation
* removeName(): void {
* this.api.creaturesDictionary$.pipe(
* // subscription handling logic
* ).subscribe(
* dictionary => this.set({creatures: dictionaryToArray(dictionary)})
* );
* }
* }
*
* @returns T[];
*
* @docsPage dictionaryToArray
* @docsCategory transformation-helpers
*/
declare function dictionaryToArray<T>(dictionary: {
[key: string]: T;
}): T[];
/**
* @description
* Merges an object of type T with updates of type Partial<T>.
* Returns a new object where updates override original values while not mutating the original one.
* @example
* interface Creature {
* id: number,
* type: string,
* name: string
* }
*
* const cat = {id: 1, type: 'cat'};
*
* const catWithname = patch(cat, {name: 'Fluffy'});
*
* // catWithname will be:
* // {id: 1, type: 'cat', name: 'Fluffy'};
*
* @example
* // Usage with RxState
*
* export class ProfileComponent {
*
* readonly changeName$ = new Subject<string>();
*
* constructor(private state: RxState<ComponentState>) {
* // Reactive implementation
* state.connect(
* this.changeName$,
* (state, name) => {
* return patch(state, { name });
* }
* );
* }
*
* // Imperative implementation
* changeName(name: string): void {
* this.state.set(patch(this.get(), { name }));
* }
* }
*
* @returns T
*
* @docsPage patch
* @docsCategory transformation-helpers
*/
declare function patch<T extends object>(object: T, upd: Partial<T>): T;
/**
* @description
* Accepts an object of type T, key of type K extends keyof T, and value of type T[K].
* Sets the property and returns a newly updated shallow copy of an object while not mutating the original one.
*
* @example
*
* const cat = {id: 1, type: 'cat', name: 'Fluffy'};
*
* const renamedCat = setProp(cat, 'name', 'Bella');
*
* // renamedCat will be:
* // {id: 1, type: 'cat', name: 'Bella'};
*
* @example
* // Usage with RxState
*
* export class ProfileComponent {
*
* readonly changeName$ = new Subject<string>();
*
* constructor(private state: RxState<ComponentState>) {
* // Reactive implementation
* state.connect(
* this.changeName$,
* (state, name) => {
* return setProp(state, 'name', name);
* }
* );
* }
*
* // Imperative implementation
* changeName(name: string): void {
* this.state.set(setProp(this.get(), 'name', name));
* }
* }
*
* @returns T
*
* @docsPage setProp
* @docsCategory transformation-helpers
*/
declare function setProp<T extends object, K extends keyof T>(object: T, key: K, value: T[K]): T;
/**
* @description
* Accepts an object of type T and single key or array of keys (K extends keyof T).
* Constructs new object based on provided keys.
*
* @example
*
* const cat = {id: 1, type: 'cat', name: 'Fluffy'};
*
* const catWithoutType = slice(cat, ['name', 'id']);
*
* // catWithoutType will be:
* // {id: 1, name: 'Fluffy'};
*
* @example
* // Usage with RxState
*
* export class AnimalsListComponent {
*
* constructor(private state: RxState<ComponentState>, private api: ApiService) {
* state.connect(
* 'animals'
* this.api.getAnimals(),
* (state, animals) => {
* return animals.map(animal => slice(animal, ['id', 'name']));
* }
* );
* }
* }
*
* @returns T
*
* @docsPage slice
* @docsCategory transformation-helpers
*/
declare function slice<T extends object, K extends keyof T>(object: T, keys: K | K[]): Pick<T, K>;
/**
* @description
* Toggles a boolean property in the object.
* Accepts object of type T and key value of which is boolean.
* Toggles the property and returns a shallow copy of an object, while not mutating the original one.
*
* @example
*
* const state = {items: [1,2,3], loading: true};
*
* const updatedState = toggle(state, 'loading');
*
* // updatedState will be:
* // {items: [1,2,3], loading: false};
*
* @example
* // Usage with RxState
*
* export class ListComponent {
* readonly loadingChange$ = new Subject();
*
* constructor(
* private state: RxState<ComponentState>
* ) {
* // Reactive implementation
* state.connect(
* this.api.loadingChange$,
* (state, _) => {
* return toggle(state, 'isLoading');
* }
* );
* }
*
* // Imperative implementation
* toggleLoading(): void {
* this.set(toggle(state, 'isLoading'));
* }
* }
*
* @returns T
*
* @docsPage toggle
* @docsCategory transformation-helpers
*/
declare function toggle<T extends object>(object: T, key: OnlyKeysOfSpecificType<T, boolean>): T;
export { deleteProp, dictionaryToArray, extract, insert, patch, remove, setProp, slice, toDictionary, toggle, update, upsert };
export type { ComparableData };