gabrielguerrero / ngrx-traits

NGRX Traits is a library to help you compose and reuse state logic in your angular app. There is two versions, @ngrx-traits/signals supports ngrx-signals, and @ngrx-traits/{core, common} supports ngrx.
MIT License
44 stars 3 forks source link

Customized Id property #85

Closed jmls closed 4 days ago

jmls commented 1 month ago

ngrx/signalstore supports entities with custom id fields

https://ngrx.io/guide/signals/signal-store/entity-management#customized-id-property

If I try and pass a type that doesn't have the id field defined, then I get a compile error with withEntitiesSingleSelection , withEntitiesMultiSelection and withEntitiesLocalFilter saying

"not assignable to type '{ id: string | number; }'."

doess ngrx-traits alllow for custom id fields ?

gabrielguerrero commented 1 month ago

Not at the moment, but it can be added, will investigate how big can the change be

gabrielguerrero commented 1 month ago

Ok this is what I found, my original idea was to add a new optional prop idKey, to all withEntities* , and this will need to be passed on to each in the same way that collection is passed, there are a few problems with this approach, for one not all of the withEntities need to change the entities so they dont all need the idKey prop, and because is optional it can cause mistakes, because a dev could forget to pass it to all withEntities that need it.

The second approach create my own withEntities something like withEntitiesCustomId({idKey, collection?}) this just stores the idKey in the store and then any withEntities* after it can used if present, this is what I'm considering at the moment

sasobunny commented 1 week ago

+1 would really need this customized Id property.

I really hope you will add this functionality. Let us know and thank you for your work!

gabrielguerrero commented 1 week ago

@sasobunny definitely will support this, there are some new changes coming in ngrx/signals 18 that will help me solve this https://github.com/ngrx/platform/pull/4399#discussion_r1651633042, so I intend to do it for the release I support version 18 of ngrx/signals

gabrielguerrero commented 5 days ago

Good news, I have it worlking, will be comming soon, first to the beta channel PR https://github.com/gabrielguerrero/ngrx-traits/pull/108 Example

const entityConfig = {
  entity: type<ProductCustom>(),
  collection: 'products',
  idKey: 'productId' as any,
} as const; // <-- IMPORTANT dont forget to add as const

export const ProductsLocalStore = signalStore(
  { providedIn: 'root' },
  withEntities(entityConfig),
  withCallStatus({ ...entityConfig, initialValue: 'loading' }),
  withEntitiesLocalPagination({
    ...entityConfig,
    pageSize: 5,
  }),
  withEntitiesLocalFilter({
    ...entityConfig,
    defaultFilter: { search: '' },
    filterFn: (entity, filter) =>
      !filter?.search ||
      entity?.name.toLowerCase().includes(filter?.search.toLowerCase()),
  }),
  withEntitiesLocalSort({
    ...entityConfig,
    defaultSort: { field: 'name', direction: 'asc' },
  }),
  withEntitiesSingleSelection({
    ...entityConfig,
  }),
  withEntitiesLoadingCall({
    ...entityConfig,
    fetchEntities: ({ productsFilter }) => {
      return inject(ProductService)
        .getProducts({
          search: productsFilter().search,
        })
        .pipe(
          map((d) =>
            d.resultList.map(({ id, ...product }) => ({
              ...product,
              productId: id,
            })),
          ),
        );
    },
  }),
);

Essentially you just need to create entityConfig as shown above and be sure to add as const, and then spread it in each withEntities store feature There is gonna be a small breaking change when ngrx/signals 18 is released, because it looks like they plan to replace idKey for a function like selectId: (entity) => entity.productId, https://github.com/ngrx/platform/pull/4399#discussion_r1651633042 but Im not sure how long will it take them to release ngrx/signals final 18, it looks like they are planning for new things like support for redux so it could take a while, so I decided it's better to release this soon, the break is very small anyway, and if it happens I will make the break change on the 18.0.0 version of this lib which will be the first to support ngrx/signals 18 (this library mayor version number will always be in sync with the mayor version of ngrx/signals it supports)

gabrielguerrero commented 5 days ago

@jmls @sasobunny this is now available in 17.9.0-beta.3

github-actions[bot] commented 4 days ago

:tada: This issue has been resolved in version 17.9.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

gabrielguerrero commented 4 days ago

One last comment, like I mentioned there are breaking changes related to custome id in ngrx/signals 18 currently in RC 2, Im already preparing support for it, and changes for ngrx-traits will be in the beta channel soon in version 18 of this lib, which will require also angular 18 and ngrx/signals 18,

basically, ngrx/signals 18 replaces idKey for selectId and adds a function called entityConfig

example of how it will look in version 18

const config = entityConfig({
  entity: type<ProductCustom>(),
  collection: 'products',
  selectId: (entity) => entity.productId, // <--  idKey is replaced by selectId function
});

export const ProductsLocalStore = signalStore(
  { providedIn: 'root' },
  withEntities(config),
  withCallStatus({ ...config, initialValue: 'loading' }),
  withEntitiesLocalPagination({
    ...config,
    pageSize: 5,
  }),
  withEntitiesLocalFilter({
    ...config,
    defaultFilter: { search: '' },
    filterFn: (entity, filter) =>
      !filter?.search ||
      entity?.name.toLowerCase().includes(filter?.search.toLowerCase()),
  }),
  withEntitiesLocalSort({
    ...config,
    defaultSort: { field: 'name', direction: 'asc' },
  }),
  withEntitiesSingleSelection({
    ...config,
  }),
  withEntitiesLoadingCall({
    ...config,
    fetchEntities: ({ productsFilter }) => {
      return inject(ProductService)
        .getProducts({
          search: productsFilter().search,
        })
        .pipe(
          map((d) =>
            d.resultList.map(({ id, ...product }) => ({
              ...product,
              productId: id,
            })),
          ),
        );
    },
  }),
);