@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
935 lines (918 loc) • 27.3 kB
JavaScript
function isKeyOf(k) {
const typeofK = typeof k;
return (k !== null &&
k !== undefined &&
['string', 'symbol', 'number'].includes(typeofK));
}
function isObjectGuard(obj) {
return (obj !== null &&
obj !== undefined &&
typeof obj === 'object' &&
!Array.isArray(obj));
}
function isDefined(val) {
return val !== null && val !== undefined;
}
/**
* @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
*/
function extract(array, keys) {
const arrayIsArray = isDefined(array) && Array.isArray(array);
if (!arrayIsArray) {
console.warn(`extract: original value (${array}) is not an array.`);
return undefined;
}
const sanitizedKeys = (Array.isArray(keys) ? keys : [keys]).filter(k => isKeyOf(k) && array.some(i => k in i));
const length = sanitizedKeys.length;
if (!sanitizedKeys.length) {
console.warn(`extract: provided keys not found`);
return undefined;
}
return array.map(item => {
let i = 0;
const result = {};
for (i; i < length; i++) {
result[sanitizedKeys[i]] = item[sanitizedKeys[i]];
}
return result;
});
}
/**
* @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
*/
function insert(source, updates) {
const updatesDefined = isDefined(updates);
const sourceIsNotArray = !Array.isArray(source);
const invalidInput = sourceIsNotArray && !updatesDefined;
if (sourceIsNotArray && isDefined(source)) {
console.warn(`Insert: Original value (${source}) is not an array.`);
}
if (invalidInput) {
return source;
}
return (sourceIsNotArray ? [] : source).concat(updatesDefined ? (Array.isArray(updates) ? updates : [updates]) : []);
}
const defaultCompareFn = (a, b) => a === b;
function valuesComparer(original, incoming, compare) {
if (isKeyOf(compare)) {
return original[compare] === incoming[compare];
}
if (Array.isArray(compare)) {
const sanitizedKeys = compare.filter((k) => isKeyOf(k));
return sanitizedKeys.length > 0
? sanitizedKeys.every((k) => original[k] === incoming[k])
: defaultCompareFn(original, incoming);
}
return (compare || defaultCompareFn)(original, incoming);
}
/**
* @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
*/
function remove(source, scrap, compare) {
const scrapAsArray = isDefined(scrap)
? Array.isArray(scrap)
? scrap
: [scrap]
: [];
const invalidInput = !Array.isArray(source);
if (invalidInput) {
console.warn(`Remove: original value (${source}) is not an array`);
return source;
}
return source.filter((existingItem) => {
return !scrapAsArray.some((item) => valuesComparer(item, existingItem, compare));
});
}
/**
* @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
*/
function toDictionary(source, key) {
if (!isDefined(source)) {
return source;
}
const sourceEmpty = !source.length;
if (!Array.isArray(source) || sourceEmpty || !isKeyOf(source[0][key])) {
if (!sourceEmpty) {
console.warn('ToDictionary: unexpected input params.');
}
return {};
}
const dictionary = {};
const length = source.length;
let i = 0;
for (i; i < length; i++) {
dictionary[`${source[i][key]}`] = Object.assign({}, source[i]);
}
return dictionary;
}
/**
* @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
*/
function update(source, updates, compare) {
const updatesDefined = updates != null;
const updatesAsArray = updatesDefined
? Array.isArray(updates)
? updates
: [updates]
: [];
const sourceDefined = source != null;
const sourceIsNotArray = !Array.isArray(source);
const invalidInput = sourceIsNotArray || source.length === 0 || updatesAsArray.length === 0;
if (sourceDefined && sourceIsNotArray) {
console.warn(`Update: Original value (${source}) is not an array.`);
}
if (invalidInput) {
return source;
}
const x = [];
for (const existingItem of source) {
const match = customFind(updatesAsArray, (item) => valuesComparer(item, existingItem, compare));
x.push(match ? { ...existingItem, ...match } : existingItem);
}
return x;
}
function customFind(array, fn) {
for (const item of array) {
const x = fn(item);
if (x) {
return item;
}
}
}
/**
* @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
*/
function upsert(source, update, compare) {
// check inputs for validity
const updatesAsArray = update != null ? (Array.isArray(update) ? update : [update]) : [];
// check inputs for validity
const sourceIsNotArray = !Array.isArray(source);
const invalidInput = sourceIsNotArray && updatesAsArray.length === 0;
// if the source value is not an Array or the input is not defined return the original source
// this is the case for any edge case:
// '', null, undefined, CustomObjectOfDoomAndDarkness, ...
if (invalidInput) {
return source;
}
// if source is empty array or not an array, but the updates are valid:
// return a shallow copy of the updates as result
if (updatesAsArray.length > 0 && (sourceIsNotArray || source.length === 0)) {
return [...updatesAsArray];
}
const inserts = [];
const updates = {};
// process updates/inserts
for (const item of updatesAsArray) {
const match = source.findIndex((sourceItem) => valuesComparer(item, sourceItem, compare));
// if item already exists, save it as update
if (match !== -1) {
updates[match] = item;
}
else {
// otherwise consider this as insert
if (isObjectGuard(item)) {
// create a shallow copy if item is an object
inserts.push({ ...item });
}
else {
// otherwise just push it
inserts.push(item);
}
}
}
const updated = source.map((item, i) => {
const updatedItem = updates[i];
// process the updated
if (updatedItem !== null && updatedItem !== undefined) {
if (isObjectGuard(item)) {
return { ...item, ...updatedItem };
}
else {
return updatedItem;
}
}
return item;
});
// return the combination of the updated source & the inserts as new array
return updated.concat(inserts);
}
/**
* @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
*/
function deleteProp(object, key) {
if (!isDefined(object) || !isObjectGuard(object)) {
console.warn(`DeleteProp: original value ${object} is not an object.`);
return object;
}
if (!isKeyOf(key)) {
console.warn(`DeleteProp: provided key is not a string, number or symbol.`);
return { ...object };
}
const copy = { ...object };
delete copy[key];
return copy;
}
/**
* @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
*/
function dictionaryToArray(dictionary) {
if (!isDefined(dictionary)) {
return dictionary;
}
if (!isObjectGuard(dictionary)) {
console.warn(`DictionaryToArray: unexpected input.`);
return [];
}
return Object.values(dictionary);
}
/**
* @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
*/
function patch(object, upd) {
const update = isObjectGuard(upd) ? upd : {};
if (!isObjectGuard(object) && isObjectGuard(upd)) {
console.warn(`Patch: original value ${object} is not an object.`);
return { ...update };
}
if (!isObjectGuard(object) && !isObjectGuard(upd)) {
console.warn(`Patch: original value ${object} and updates ${upd} are not objects.`);
return object;
}
return { ...object, ...update };
}
/**
* @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
*/
function setProp(object, key, value) {
const objectIsObject = isObjectGuard(object);
const keyIsValid = isKeyOf(key);
const initialObject = objectIsObject ? object : {};
if (!objectIsObject) {
console.warn(`SetProp: original value (${object}) is not an object.`);
}
if (!keyIsValid) {
console.warn(`SetProp: key argument (${key}) is invalid.`);
}
if (!isDefined(object) && !keyIsValid) {
return object;
}
if (keyIsValid) {
return {
...initialObject,
[key]: value
};
}
return { ...initialObject };
}
/**
* @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
*/
function slice(object, keys) {
const objectIsObject = isDefined(object) && isObjectGuard(object);
if (!objectIsObject) {
console.warn(`slice: original value (${object}) is not an object.`);
return undefined;
}
const sanitizedKeys = (Array.isArray(keys) ? keys : [keys]).filter((k) => isKeyOf(k) && k in object);
if (!sanitizedKeys.length) {
console.warn(`slice: provided keys not found`);
return undefined;
}
return sanitizedKeys.reduce((acc, k) => ({ ...acc, [k]: object[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
*/
function toggle(object, key) {
const objectIsObject = isObjectGuard(object);
const keyIsValid = isKeyOf(key);
const initialObject = objectIsObject ? object : {};
if (!objectIsObject) {
console.warn(`Toggle: original value (${object}) is not an object.`);
}
if (!keyIsValid) {
console.warn(`Toggle: key argument (${key}) is invalid.`);
}
if (keyIsValid && typeof initialObject[key] !== 'boolean') {
console.warn(`Toggle: value of the key (${String(key)}) is not a boolean.`);
}
if (!isDefined(object) && !keyIsValid) {
return object;
}
if (keyIsValid &&
(typeof initialObject[key] === 'boolean' ||
!initialObject.hasOwnProperty(key))) {
return { ...initialObject, [key]: !initialObject[key] };
}
return { ...initialObject };
}
/**
* Generated bundle index. Do not edit.
*/
export { deleteProp, dictionaryToArray, extract, insert, patch, remove, setProp, slice, toDictionary, toggle, update, upsert };
//# sourceMappingURL=cdk-transformations.mjs.map