diff --git a/libs/angular/cookies/package.json b/libs/angular/cookies/package.json index 0ec895a..54d61af 100644 --- a/libs/angular/cookies/package.json +++ b/libs/angular/cookies/package.json @@ -24,7 +24,6 @@ "peerDependencies": { "@angular/core": "^21.0.9", "rxjs": "~7.8.0", - "lodash": "^4.17.21", "vanilla-cookieconsent": "^3.1.0", "@ibenvandeveire/ngx-core": "^21.0.0" }, diff --git a/libs/angular/cookies/src/lib/directives/has-cookie/has-cookie.directive.ts b/libs/angular/cookies/src/lib/directives/has-cookie/has-cookie.directive.ts index de872f5..2a3aeb2 100644 --- a/libs/angular/cookies/src/lib/directives/has-cookie/has-cookie.directive.ts +++ b/libs/angular/cookies/src/lib/directives/has-cookie/has-cookie.directive.ts @@ -11,7 +11,6 @@ import { input, InputSignal, } from '@angular/core'; -import { flatten } from 'lodash'; import { Subject, tap, takeUntil, combineLatest, map } from 'rxjs'; import { NgxCookiesFallBackComponent } from '../../abstracts'; @@ -119,7 +118,7 @@ export class NgxHasCookieDirective implements OnDestroy { ) .pipe( map((hasCookies) => { - return flatten(hasCookies).every((hasCookie) => hasCookie); + return hasCookies.flat().every((hasCookie) => hasCookie); }), tap((hasCookie) => { // Iben: Clear the current view diff --git a/libs/angular/forms/package.json b/libs/angular/forms/package.json index d234e23..73629ca 100644 --- a/libs/angular/forms/package.json +++ b/libs/angular/forms/package.json @@ -39,7 +39,7 @@ "@angular/core": "^21.0.9", "@angular/forms": "^21.0.9", "rxjs": "~7.8.0", - "lodash": "^4.17.21", + "remeda": "^2.39.0", "@angular/router": "^21.0.9", "obj-clean": "^3.0.1", "date-fns": "^4.1.0" diff --git a/libs/angular/forms/src/lib/abstracts/data-form/data-form.accessor.ts b/libs/angular/forms/src/lib/abstracts/data-form/data-form.accessor.ts index 6db5b99..09b9ad3 100644 --- a/libs/angular/forms/src/lib/abstracts/data-form/data-form.accessor.ts +++ b/libs/angular/forms/src/lib/abstracts/data-form/data-form.accessor.ts @@ -1,7 +1,7 @@ import { Directive, input, InputSignal, OnDestroy, OnInit } from '@angular/core'; import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; import { AbstractControl, FormControl } from '@angular/forms'; -import { isEqual } from 'lodash'; +import { isDeepEqual } from 'remeda'; import { Observable, of, Subject } from 'rxjs'; import { switchMap, takeUntil, tap } from 'rxjs/operators'; @@ -12,7 +12,7 @@ export abstract class DataFormAccessor< ConstructionDataType = unknown, DataType = unknown, FormAccessorFormType extends AbstractControl = FormControl, - FormValueType = DataType, + FormValueType = DataType > extends NgxFormsControlValueAccessor implements OnDestroy, OnInit @@ -50,7 +50,7 @@ export abstract class DataFormAccessor< const currentFormValue = this.form?.getRawValue(); // Iben: If we already have current data and the current data matches the new data, we don't make a new form - if (this.currentData && isEqual(this.currentData, data)) { + if (this.currentData && isDeepEqual(this.currentData, data)) { this.currentData = data; return of(); } diff --git a/libs/angular/inform/package.json b/libs/angular/inform/package.json index e81de34..db545c1 100644 --- a/libs/angular/inform/package.json +++ b/libs/angular/inform/package.json @@ -40,7 +40,7 @@ "@angular/router": "^21.0.9", "@ibenvandeveire/ngx-core": "^21.0.0", "@angular/common": "^21.0.9", - "lodash": "^4.17.21" + "remeda": "^2.39.0" }, "sideEffects": false } diff --git a/libs/angular/inform/src/lib/services/toast/toast.service.ts b/libs/angular/inform/src/lib/services/toast/toast.service.ts index 3cce806..903b352 100644 --- a/libs/angular/inform/src/lib/services/toast/toast.service.ts +++ b/libs/angular/inform/src/lib/services/toast/toast.service.ts @@ -1,6 +1,6 @@ import { computed, inject, Injectable, Signal, Type } from '@angular/core'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; -import { uniqBy } from 'lodash'; +import { uniqueBy } from 'remeda'; import { BehaviorSubject, combineLatest, @@ -65,7 +65,7 @@ export class NgxToastService { combineLatest([this.queue$, this.showBundledToasts$]).pipe( map(([toasts, showBundled]) => { // Iben: Prevent duplicates - const result = uniqBy(toasts, (item) => item.id) || []; + const result = uniqueBy(toasts, (item) => item.id) || []; // Iben: If there is no max amount, we return the toasts as is if (!this.configuration.maxAmount || showBundled) { diff --git a/libs/angular/layout/package.json b/libs/angular/layout/package.json index 61c2a38..ff6c78a 100644 --- a/libs/angular/layout/package.json +++ b/libs/angular/layout/package.json @@ -53,7 +53,7 @@ "uuid": "^11.1.0", "@angular/cdk": "^21.0.6", "@angular/forms": "^21.0.9", - "lodash": "^4.17.21", + "remeda": "^2.39.0", "markerjs2": "^2.32.4", "markerjs-live": "^1.2.1", "@ibenvandeveire/ngx-core": "^21.0.0", diff --git a/libs/angular/layout/src/lib/components/table/ngx-table.component.ts b/libs/angular/layout/src/lib/components/table/ngx-table.component.ts index 307ffc6..beea9c1 100644 --- a/libs/angular/layout/src/lib/components/table/ngx-table.component.ts +++ b/libs/angular/layout/src/lib/components/table/ngx-table.component.ts @@ -31,7 +31,7 @@ import { NG_VALUE_ACCESSOR, ReactiveFormsModule, } from '@angular/forms'; -import { isEmpty } from 'lodash'; +import { isEmptyish } from 'remeda'; import { Subject } from 'rxjs'; import { takeUntil, tap } from 'rxjs/operators'; @@ -726,7 +726,7 @@ export class NgxTableComponent private handleCurrentSort(event: NgxTableSortEvent): void { // Iben: Early exit if the sortable cell record is empty or if the cell already has the sortDirection of the event if ( - isEmpty( + isEmptyish( this.sortableTableCellRecord() || (event && this.sortableTableCellRecord()[event.column].sortDirection === diff --git a/libs/angular/layout/src/lib/pipes/get-pipe/get.pipe.ts b/libs/angular/layout/src/lib/pipes/get-pipe/get.pipe.ts index c9ee8f0..088556c 100644 --- a/libs/angular/layout/src/lib/pipes/get-pipe/get.pipe.ts +++ b/libs/angular/layout/src/lib/pipes/get-pipe/get.pipe.ts @@ -1,5 +1,4 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { get } from 'lodash'; /** * This pipe returns a value based on a provided path @@ -33,6 +32,11 @@ export class NgxTableGetPipe implements PipeTransform { } // Iben: Return the property - return get(value, path); + return path + .split('.') + .reduce( + (acc, key) => (acc == null ? undefined : (acc as Record)[key]), + value + ); } } diff --git a/libs/angular/layout/src/lib/services/mobile-layout/mobile-layout.service.ts b/libs/angular/layout/src/lib/services/mobile-layout/mobile-layout.service.ts index 5fd7163..1c3bfb5 100644 --- a/libs/angular/layout/src/lib/services/mobile-layout/mobile-layout.service.ts +++ b/libs/angular/layout/src/lib/services/mobile-layout/mobile-layout.service.ts @@ -1,5 +1,4 @@ import { inject, Injectable, signal, Signal, WritableSignal } from '@angular/core'; -import { get } from 'lodash'; import clean from 'obj-clean'; import { BehaviorSubject, @@ -67,16 +66,15 @@ export class NgxMobileLayoutService { */ protected readonly showFlyout: WritableSignal = signal(false); - /** - * Whether the currently opened flyout should be preserved when the route changes - */ - protected readonly preserveFlyout: WritableSignal = signal( false ); + /** + * Whether the currently opened flyout should be preserved when the route changes + */ + protected readonly preserveFlyout: WritableSignal = signal(false); /** * Whether the aside should be shown */ - protected readonly showAside: WritableSignal = signal( false ); - + protected readonly showAside: WritableSignal = signal(false); /** * An array of queries @@ -138,15 +136,15 @@ export class NgxMobileLayoutService { return this.initialLayoutSet$.pipe( filter(Boolean), take(1), - tap( () => { - // Iben: Preserve the flyout if it was required - const result = this.preserveFlyout() - ? { ...layout, flyout: this.layoutSubject$.value.flyout } - : layout; + tap(() => { + // Iben: Preserve the flyout if it was required + const result = this.preserveFlyout() + ? { ...layout, flyout: this.layoutSubject$.value.flyout } + : layout; - // Iben: Update the layout - this.layoutSubject$.next(extractLayout(result, this.defaultLayout, this.queries)); - }) + // Iben: Update the layout + this.layoutSubject$.next(extractLayout(result, this.defaultLayout, this.queries)); + }) ); } @@ -157,7 +155,11 @@ export class NgxMobileLayoutService { * @param params - An optional set of parameters for the flyout * */ - public openFlyout(flyout?: ComponentType, params?: NgxMobileLayoutOutletParams, preserveFlyout: boolean = false): void { + public openFlyout( + flyout?: ComponentType, + params?: NgxMobileLayoutOutletParams, + preserveFlyout: boolean = false + ): void { // Iben: Add the flyout if there wasn't one defined if (flyout) { this.layoutSubject$.next({ @@ -171,8 +173,8 @@ export class NgxMobileLayoutService { }); // Iben: Make the flyout visible and add the injector - this.showFlyout.set( true ); - this.preserveFlyout.set(preserveFlyout) + this.showFlyout.set(true); + this.preserveFlyout.set(preserveFlyout); this.flyoutParams.set(params); } } @@ -182,8 +184,8 @@ export class NgxMobileLayoutService { */ public closeFlyout(): void { // Iben: Make the flyout invisible - this.preserveFlyout.set( false ); - this.showFlyout.set( false ); + this.preserveFlyout.set(false); + this.showFlyout.set(false); this.flyoutParams.set(undefined); } @@ -223,17 +225,18 @@ export class NgxMobileLayoutService { /** * Provides an initial layout if one was provided */ - public setUpInitialLayout ( markAsInitial: boolean = true ): void { - + public setUpInitialLayout(markAsInitial: boolean = true): void { // Iben: Set up the initial queries and set it as 'default' if this.queries = this.mediaService.queries.length ? this.mediaService.queries.map((query) => query.toLowerCase()) : ['default']; - // Iben: Preserve the flyout if it was required - const layout = this.preserveFlyout() ? { ...this.defaultLayout,flyout: this.layoutSubject$.value.flyout } : this.defaultLayout; + // Iben: Preserve the flyout if it was required + const layout = this.preserveFlyout() + ? { ...this.defaultLayout, flyout: this.layoutSubject$.value.flyout } + : this.defaultLayout; - // Iben: Set initial layout + // Iben: Set initial layout this.layoutSubject$.next(extractLayout(layout, {}, this.queries)); // Iben: Mark the initial layout set as true @@ -251,7 +254,17 @@ export class NgxMobileLayoutService { return this.layout$.pipe( filter(Boolean), distinctUntilChanged(), - map((layout) => Boolean(get(layout, element))) + map((layout) => + Boolean( + element + .split('.') + .reduce( + (acc, key) => + acc == null ? undefined : (acc as Record)[key], + layout + ) + ) + ) ); } } diff --git a/libs/angular/store/package.json b/libs/angular/store/package.json index 83ec9b2..cad54ec 100644 --- a/libs/angular/store/package.json +++ b/libs/angular/store/package.json @@ -28,14 +28,10 @@ "@angular/common": "^21.0.9", "@angular/core": "^21.0.9", "rxjs": "~7.8.0", - "lodash": "^4.17.21", "@ngrx/effects": "^21.0.1", "@ngrx/entity": "^21.0.1", "@ngrx/signals": "^21.0.1", "@ngrx/store": "^21.0.1" }, - "sideEffects": false, - "devDependencies": { - "@types/lodash": "^4.14.195" - } + "sideEffects": false } diff --git a/libs/angular/store/src/lib/rxjs/utils/base-store/base-store.util.ts b/libs/angular/store/src/lib/rxjs/utils/base-store/base-store.util.ts index 0a6b29b..0ad1328 100644 --- a/libs/angular/store/src/lib/rxjs/utils/base-store/base-store.util.ts +++ b/libs/angular/store/src/lib/rxjs/utils/base-store/base-store.util.ts @@ -1,6 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; import { createAction, createReducer, createSelector, on, props } from '@ngrx/store'; -import { get } from 'lodash'; import { BaseStore, BaseStoreAssets, BaseStoreAssetsGeneratorOptions } from '../../interfaces'; import { BaseStoreEffectsInterface } from '../../interfaces/effects'; @@ -9,10 +8,11 @@ import { BaseStoreEffectsInterface } from '../../interfaces/effects'; * Creates store assets to save basic properties (object, string, number, etc.) into the store * * @param slice - The slice we wish to save the data in + * @param initialStateValue - The actual value of the initial state */ export const createBaseStoreAssets = < StateInterface, - EffectsInterface extends BaseStoreEffectsInterface = any, + EffectsInterface extends BaseStoreEffectsInterface = any >({ slice, initialStateValue, @@ -72,7 +72,8 @@ export const createBaseStoreAssets = < ); // Iben: Create selectors - const featureSelector = (state) => get(state, slice); + const featureSelector = (state) => + slice.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), state); const selectors = { select: createSelector(featureSelector, (state: BaseStore) => state.data), diff --git a/libs/angular/store/src/lib/rxjs/utils/entity-store/entity-adapter.util.ts b/libs/angular/store/src/lib/rxjs/utils/entity-store/entity-adapter.util.ts index c49d1a9..22657f9 100644 --- a/libs/angular/store/src/lib/rxjs/utils/entity-store/entity-adapter.util.ts +++ b/libs/angular/store/src/lib/rxjs/utils/entity-store/entity-adapter.util.ts @@ -1,6 +1,5 @@ import { EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import { createAction, createReducer, createSelector, on, props } from '@ngrx/store'; -import { get } from 'lodash'; import { BasicEntityState, @@ -82,7 +81,8 @@ export const createEntityAdapterStoreAssets = < }; // Iben: Create state selectors - const featureSelector = (state): any => get(state, slice); + const featureSelector = (state): any => + slice.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), state); const selectors = { selectAll: createSelector(featureSelector, adapter.getSelectors().selectAll), diff --git a/libs/angular/utils/package.json b/libs/angular/utils/package.json index 7c5b0ea..b3cc6ec 100644 --- a/libs/angular/utils/package.json +++ b/libs/angular/utils/package.json @@ -39,8 +39,7 @@ "@angular/router": "^21.0.9", "rxjs": "~7.8.0", "@angular/platform-browser": "^21.0.9", - "@ibenvandeveire/ngx-core": "^21.0.0", - "lodash": "^4.17.21" + "@ibenvandeveire/ngx-core": "^21.0.0" }, "sideEffects": false } diff --git a/libs/angular/utils/src/lib/directives/cypress-tag/cypress-tag.directive.ts b/libs/angular/utils/src/lib/directives/cypress-tag/cypress-tag.directive.ts index 9199ffd..c181d63 100644 --- a/libs/angular/utils/src/lib/directives/cypress-tag/cypress-tag.directive.ts +++ b/libs/angular/utils/src/lib/directives/cypress-tag/cypress-tag.directive.ts @@ -1,11 +1,4 @@ -import { - computed, - Directive, - input, - InputSignal, - Signal, -} from '@angular/core'; -import { get } from 'lodash'; +import { computed, Directive, input, InputSignal, Signal } from '@angular/core'; @Directive({ selector: '[cypressTag]', @@ -16,7 +9,12 @@ import { get } from 'lodash'; }) export class NgxCypressTagDirective { public tag: Signal = computed(() => { - return get({}, this.cypressTag()); + return this.cypressTag() + .split('.') + .reduce( + (acc, key) => (acc == null ? undefined : (acc as Record)[key]), + {} + ) as string; }); /** diff --git a/libs/javascript/rxjs/package.json b/libs/javascript/rxjs/package.json index 7052d46..441b24b 100644 --- a/libs/javascript/rxjs/package.json +++ b/libs/javascript/rxjs/package.json @@ -1,31 +1,31 @@ { - "name": "@ibenvandeveire/rxjs-utils", - "version": "1.0.0", - "description": "A package containing common RXJS use-cases, created by Iben Van de Veire.", - "keywords": [ - "rxjs", - "pluck", - "operators", - "operator", - "fetch-if", - "combineBoolean", - "populate", - "array" - ], - "license": "MIT", - "author": { - "name": "Iben Van de Veire", - "url": "https://github.com/IbenTesara" - }, - "repository": { - "type": "git", - "url": "https://github.com/IbenTesara/opensource", - "directory": "libs/javascript/rxjs/src/lib" - }, - "main": "./index.cjs", - "types": "./index.d.ts", - "dependencies": { - "rxjs": "~7.8.0", - "lodash": "^4.17.21" - } + "name": "@ibenvandeveire/rxjs-utils", + "version": "1.0.0", + "description": "A package containing common RXJS use-cases, created by Iben Van de Veire.", + "keywords": [ + "rxjs", + "pluck", + "operators", + "operator", + "fetch-if", + "combineBoolean", + "populate", + "array" + ], + "license": "MIT", + "author": { + "name": "Iben Van de Veire", + "url": "https://github.com/IbenTesara" + }, + "repository": { + "type": "git", + "url": "https://github.com/IbenTesara/opensource", + "directory": "libs/javascript/rxjs/src/lib" + }, + "main": "./index.cjs", + "types": "./index.d.ts", + "dependencies": { + "rxjs": "~7.8.0", + "remeda": "^2.39.0" + } } diff --git a/libs/javascript/rxjs/src/lib/operators/index.ts b/libs/javascript/rxjs/src/lib/operators/index.ts index 6c2fd96..2dbbbe2 100644 --- a/libs/javascript/rxjs/src/lib/operators/index.ts +++ b/libs/javascript/rxjs/src/lib/operators/index.ts @@ -1,6 +1,6 @@ export * from './pluck-or/pluck-or.operator'; export * from './combine-boolean/combine-boolean.operator'; export * from './pluck/pluck.operator'; -export * from './populate/populate.operator'; +export { populate } from './populate/populate.operator'; export * from './fetch-if/fetch-if.operator'; export * from './array'; diff --git a/libs/javascript/rxjs/src/lib/operators/pluck/pluck.operator.ts b/libs/javascript/rxjs/src/lib/operators/pluck/pluck.operator.ts index 5d6fdfd..c9dce9d 100644 --- a/libs/javascript/rxjs/src/lib/operators/pluck/pluck.operator.ts +++ b/libs/javascript/rxjs/src/lib/operators/pluck/pluck.operator.ts @@ -1,4 +1,3 @@ -import { getOr } from 'lodash/fp'; import { OperatorFunction } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -22,10 +21,16 @@ export const pluck = ( return map((value) => { // Iben: Return an empty value if the value is undefined if (!value) { - return; + return undefined as PropertyType; } // Iben: Return the value of the property - return getOr(undefined, properties, value); + return properties.reduce( + (accumulator, property) => + accumulator == null + ? undefined + : (accumulator as Record)[property], + value + ) as PropertyType; }); }; diff --git a/libs/javascript/rxjs/src/lib/operators/populate/populate.operator.spec.ts b/libs/javascript/rxjs/src/lib/operators/populate/populate.operator.spec.ts index 0276d13..bdb99bd 100644 --- a/libs/javascript/rxjs/src/lib/operators/populate/populate.operator.spec.ts +++ b/libs/javascript/rxjs/src/lib/operators/populate/populate.operator.spec.ts @@ -1,5 +1,71 @@ import { of } from 'rxjs'; -import { populate } from './populate.operator'; +import { getByPath, populate, setByPath } from './populate.operator'; + +describe('getByPath', () => { + it('should return the value of a top-level property', () => { + expect(getByPath({ title: 'Test' }, 'title')).toEqual('Test'); + }); + + it('should return the value of a nested property', () => { + expect(getByPath({ ads: { adsId: '1' } }, 'ads.adsId')).toEqual('1'); + }); + + it('should return undefined if the path does not exist', () => { + expect(getByPath({ title: 'Test' }, 'description')).toBeUndefined(); + }); + + it('should return undefined if an intermediate segment of the path does not exist', () => { + expect(getByPath({ title: 'Test' }, 'ads.adsId')).toBeUndefined(); + }); + + it('should return undefined if the data itself is null or undefined', () => { + expect(getByPath(null, 'title')).toBeUndefined(); + expect(getByPath(undefined, 'title')).toBeUndefined(); + }); +}); + +describe('setByPath', () => { + it('should set a top-level property', () => { + const target = { title: 'Test' }; + + setByPath(target, 'title', 'Updated'); + + expect(target).toEqual({ title: 'Updated' }); + }); + + it('should set a nested property when the intermediate object already exists', () => { + const target = { ads: { adsId: '1' } }; + + setByPath(target, 'ads.items', ['1']); + + expect(target).toEqual({ ads: { adsId: '1', items: ['1'] } }); + }); + + it('should create intermediate objects when they do not exist yet', () => { + const target: Record = {}; + + setByPath(target, 'hello.world', 'value'); + + expect(target).toEqual({ hello: { world: 'value' } }); + }); + + it('should overwrite a non-object intermediate value when setting a deeper path', () => { + const target: Record = { ads: 'not-an-object' }; + + setByPath(target, 'ads.items', ['1']); + + expect(target).toEqual({ ads: { items: ['1'] } }); + }); + + it('should mutate the provided target in place', () => { + const target: Record = {}; + + const result = setByPath(target, 'title', 'Test'); + + expect(result).toBeUndefined(); + expect(target).toEqual({ title: 'Test' }); + }); +}); describe('populate', () => { describe('Default populateIf', () => { diff --git a/libs/javascript/rxjs/src/lib/operators/populate/populate.operator.ts b/libs/javascript/rxjs/src/lib/operators/populate/populate.operator.ts index 5b8286f..2152002 100644 --- a/libs/javascript/rxjs/src/lib/operators/populate/populate.operator.ts +++ b/libs/javascript/rxjs/src/lib/operators/populate/populate.operator.ts @@ -1,6 +1,48 @@ -import { cloneDeep, get, set } from 'lodash'; +import { clone } from 'remeda'; import { Observable, OperatorFunction, combineLatest, map, of, switchMap } from 'rxjs'; +/** + * getByPath + * + * Reads the value at a dot-separated path in an object + * + * @param data - The object we wish to read the value from + * @param path - A dot-separated path to the property, e.g. 'ads.items' + * @returns The value at the path, or undefined if it does not exist + */ +export const getByPath = (data: unknown, path: string): unknown => + path + .split('.') + .reduce( + (acc, key) => (acc == null ? undefined : (acc as Record)[key]), + data + ); + +/** + * setByPath + * + * Sets the value at a dot-separated path in an object, mutating it in place + * + * Any missing intermediate objects along the path are created as needed + * + * @param target - The object we wish to set the value on + * @param path - A dot-separated path to the property, e.g. 'ads.items' + * @param value - The value we wish to set at the path + */ +export const setByPath = (target: Record, path: string, value: unknown): void => { + const keys = path.split('.'); + const lastKey = keys.pop() as string; + const parent = keys.reduce>((acc, key) => { + if (typeof acc[key] !== 'object' || acc[key] === null) { + acc[key] = {}; + } + + return acc[key] as Record; + }, target); + + parent[lastKey] = value; +}; + //TODO: Iben: Find out a way to type this better than with any, but without introducing a complex typing hell /** * Populates an existing data source with additional data from provided observables @@ -15,7 +57,7 @@ export const populate = ( return switchMap((data) => { // Iben: Filter out all keys that are populated const keys = Object.keys(populater).filter((key) => { - const value = get(data, key); + const value = getByPath(data, key); // Iben: If no populateIf function is provided, we check if the value is undefined or null if (!populateIf) { @@ -37,12 +79,12 @@ export const populate = ( // Iben: Loop over the provided observables and populate the data return combineLatest(observables).pipe( map((results) => { - // Iben: Use cloneDeep to avoid issues with readonly properties - const result = cloneDeep(data) as DataType; + // Iben: Use clone to avoid issues with readonly properties + const result = clone(data) as DataType; // Iben: Loop over the results and merge them into the value results.forEach((value, index) => { - set(result, keys[index], value); + setByPath(result as Record, keys[index], value); }); return result; diff --git a/package-lock.json b/package-lock.json index 2bd7e21..1f533a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,11 +26,11 @@ "@ngx-translate/core": "17.0.0", "date-fns": "^4.1.0", "deepmerge-ts": "^7.1.5", - "lodash": "^4.17.21", "marked": "^18.0.0", "markerjs-live": "^1.2.1", "markerjs2": "^2.32.4", "obj-clean": "^3.0.1", + "remeda": "^2.39.0", "rxjs": "~7.8.0", "uuid": "^11.1.0", "vanilla-cookieconsent": "^3.1.0", @@ -3356,6 +3356,33 @@ } } }, + "node_modules/@compodoc/compodoc/node_modules/@angular-devkit/schematics/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/@compodoc/compodoc/node_modules/@babel/core": { "version": "7.25.8", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", @@ -3716,6 +3743,36 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@compodoc/compodoc/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/@compodoc/compodoc/node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@compodoc/compodoc/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -24914,7 +24971,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.clonedeepwith": { "version": "4.5.0", @@ -29692,6 +29750,18 @@ "regjsparser": "bin/parser" } }, + "node_modules/remeda": { + "version": "2.39.0", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.39.0.tgz", + "integrity": "sha512-3Ki8dU1o3OVu4dwIQ2Pj+yiuP7OnEbmWAGmJ3yDRqopily5jsj8NWzPvbS89H85d6UdONKEcUnrfuHY6jN9vyw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remeda" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -37341,6 +37411,24 @@ "rxjs": "7.8.1", "source-map": "0.7.4" } + }, + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } } } }, @@ -37597,6 +37685,27 @@ "minipass": "^7.1.2" } }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "picomatch": "^2.2.1" + }, + "dependencies": { + "picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "optional": true, + "peer": true + } + } + }, "rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -51601,7 +51710,8 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "lodash.clonedeepwith": { "version": "4.5.0", @@ -54653,6 +54763,11 @@ "jsesc": "~3.1.0" } }, + "remeda": { + "version": "2.39.0", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.39.0.tgz", + "integrity": "sha512-3Ki8dU1o3OVu4dwIQ2Pj+yiuP7OnEbmWAGmJ3yDRqopily5jsj8NWzPvbS89H85d6UdONKEcUnrfuHY6jN9vyw==" + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index a28bde1..f93640a 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,11 @@ "@ngx-translate/core": "17.0.0", "date-fns": "^4.1.0", "deepmerge-ts": "^7.1.5", - "lodash": "^4.17.21", "marked": "^18.0.0", "markerjs-live": "^1.2.1", "markerjs2": "^2.32.4", "obj-clean": "^3.0.1", + "remeda": "^2.39.0", "rxjs": "~7.8.0", "uuid": "^11.1.0", "vanilla-cookieconsent": "^3.1.0",