-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
Which @ngrx/* package(s) are relevant/related to the feature request?
entity
Information
Problem
When using createEntityAdapter with a custom selectId function, the adapter.selectId property is typed as IdSelector<T>, which returns string | number. This union type creates friction when reusing selectId elsewhere in the codebase, as it requires type guards or type casting at every usage site.
Current Behavior
import { createEntityAdapter, EntityState, IdSelector } from '@ngrx/entity';
interface Product {
id: number;
name: string;
}
const adapter = createEntityAdapter<Product>({
selectId: (product) => product.id, // clearly returns number
});
// adapter.selectId is typed as IdSelector<Product> = (model: Product) => string | number
const productId = adapter.selectId(someProduct);
// productId is string | number, even though we know it's always number
// This requires type guards or casting everywhere:
if (typeof productId === 'number') {
// use productId
}
// or
const productId = adapter.selectId(someProduct) as number;Desired Behavior
Previously, @ngrx/entity exported IdSelectorNum<T> and IdSelectorStr<T> types from @ngrx/entity/src/models, which allowed for cleaner type casting at the definition site:
import { IdSelectorNum } from '@ngrx/entity/src/models';
export const productSelectId = adapter.selectId as IdSelectorNum<Product>;
// Now productSelectId returns number, no casting needed at usage sitesProposed Solution
One of the following approaches would improve the developer experience:
-
Re-export
IdSelectorNum<T>andIdSelectorStr<T>types from the public API (@ngrx/entity) so developers can cast once at the definition site. -
Add generic parameter to
createEntityAdapterto specify the ID type:
const adapter = createEntityAdapter<Product, number>({
selectId: (product) => product.id,
});
// adapter.selectId now returns number- Infer the ID type from the
selectIdfunction automatically:
const adapter = createEntityAdapter<Product>({
selectId: (product) => product.id, // TypeScript infers number
});
// adapter.selectId should be (model: Product) => numberOption 3 would provide the best developer experience with zero additional effort, but any of these solutions would significantly improve type safety when working with entity adapters.
Describe any alternatives/workarounds you're currently using
Currently, we define custom type aliases and cast manually:
// Workaround: Define our own types
type IdSelectorNum<T> = (model: T) => number;
type IdSelectorStr<T> = (model: T) => string;
// Cast at definition site
export const productSelectId = adapter.selectId as IdSelectorNum<Product>;This works but has drawbacks:
- Each project needs to define these types locally
- It's easy to make mistakes with manual casting
- New team members may not know about this pattern
I would be willing to submit a PR to fix this issue
- Yes
- No