I have an ngrx/signalStore that contains fruits as entities. For proper usage of the signalStore, you need to have an id
field in the Fruit class, but the API I use retrieves fruits that have a field fruitId
. Is there a way to tell the store that it will be the primary key for the class instead of introducing a duplicated field named id
?
Here is my store:
export const FruitStore = signalStore(
{ providedIn: 'root' },
withEntities<Fruit>(),
withMethods((
store,
fruitService = inject(FruitService),
) => ({
loadFruits: rxMethod<void>(
pipe(
exhaustMap(() => {
return fruitService.getFruits().pipe(
tapResponse({
next: (fruits) => {
patchState(store, setAllEntities(fruits);
},
error: (error: { message: string }) => {
console.log(error.message);
}
}),
);
}),
)
),
})),
withHooks({
onInit({ loadFruits }) {
loadFruits();
}
}),
);
My Fruit class:
export class Fruit {
fruitId: number;
name: string;
imageURL: string;
}
My current solution is to add a duplicated field to the class (Fruit class comes from the API generated with OpenAPI, so I can not change it). But I'm not fully satisfied with this solution. Do you have any ideas to improve it?
export type WithId<T> = T & { id: number };
Changes in the store:
export const FruitStore = signalStore(
{ providedIn: 'root' },
// Extend Fruit class 'WithId'
withEntities<WithId<Fruit>>(),
withMethods((
store,
fruitService = inject(FruitService),
) => ({
loadFruits: rxMethod<void>(
pipe(
exhaustMap(() => {
return fruitService.getFruits().pipe(
tapResponse({
next: (fruits) => {
// Add the duplicated field
patchState(store, setAllEntities(fruits.map(fruit => ({ ...fruit, id: fruit.fruitId })));
},
error: (error: { message: string }) => {
console.log(error.message);
}
}),
);
}),
)
),
})),
withHooks({
onInit({ loadFruits }) {
loadFruits();
}
}),
);
When working with angular 17, you need to specify the idKey
property in the second argument object of setAllEntities
.
...
withMethods((store) => {
const todoService = inject(TodoService);
return {
loadTodos: rxMethod<void>(
pipe(
exhaustMap(() => {
return todoService.getTodos().pipe(
tap((fruits) => {
// Add the duplicated field
patchState(
store,
setAllEntities<Todo>(
fruits.map((fruit) => ({ ...fruit })),
{ idKey: 'todoId' }
)
);
})
);
})
)
),
...
When you are working with latest version of angular (17+). The process is present in the documentation.
import { patchState, signalStore, withMethods } from '@ngrx/signals';
import {
addEntities,
removeEntity,
SelectEntityId,
setEntity,
updateAllEntities,
withEntities,
} from '@ngrx/signals/entities';
type Todo = {
key: number;
text: string;
completed: boolean;
};
const selectId: SelectEntityId<Todo> = (todo) => todo.key;
export const TodosStore = signalStore(
withEntities<Todo>(),
withMethods((store) => ({
addTodos(todos: Todo[]): void {
patchState(store, addEntities(todos, { selectId }));
},
setTodo(todo: Todo): void {
patchState(store, setEntity(todo, { selectId }));
},
completeAllTodos(): void {
patchState(
store,
updateAllEntities({ completed: true }, { selectId })
);
},
removeTodo(key: number): void {
patchState(store, removeEntity(key));
},
}))
);