Entities
This feature enables the store to act as an entities store. You can think of an entities
state as a table in a database,
where each table represents a flat collection of similar entities. Elf's entities state simplifies the process, giving you
everything you need to manage it.
First, you need to install the package by using the CLI command elf-cli install
and selecting the entities package,
or via npm:
npm i @ngneat/elf-entities
To use this feature, provide the withEntities
props factory function in the createStore
call:
import { createStore } from '@ngneat/elf';
import { withEntities } from '@ngneat/elf-entities';
interface Todo {
id: number;
label: string;
}
const todosStore = createStore({ name: 'todos' }, withEntities<Todo>());
This will allow you to use the following ready-made mutations and queries:
Queries
selectAllEntities
Select the entire store's entity collection:
import { selectAllEntities } from '@ngneat/elf-entities';
const todos$ = todosStore.pipe(selectAllEntities());
selectAllEntitiesApply
Select the entire store's entity collection, and apply a filter/map
:
import { selectAllEntitiesApply } from '@ngneat/elf-entities';
const titles$ = todosStore.pipe(
selectAllEntitiesApply({
mapEntity: (e) => e.title,
filterEntity: (e) => e.completed,
})
);
In the above example, it'll first apply the filter
and then the map
function.
getAllEntitiesApply
Get the entire store's entity collection, and apply a filter/map
:
import { getAllEntitiesApply } from '@ngneat/elf-entities';
const titles = todosStore.query(
getAllEntitiesApply({
mapEntity: (e) => e.title,
filterEntity: (e) => e.completed,
})
);
selectEntities
Select the entire store's entity collection as object:
import { selectEntities } from '@ngneat/elf-entities';
const todos$ = todosStore.pipe(selectEntities());
selectEntity
Select an entity or a slice of an entity:
import { selectEntity } from '@ngneat/elf-entities';
const todo$ = todosStore.pipe(selectEntity(id));
const title$ = todosStore.pipe(selectEntity(id, { pluck: 'title' }));
const title$ = todosStore.pipe(selectEntity(id, { pluck: (e) => e.title }));
selectEntityByPredicate
Select an entity from the store by predicate:
import { selectEntityByPredicate } from '@ngneat/elf-entities';
const todo$ = todosStore.pipe(
selectEntityByPredicate(({ completed }) => !completed)
);
const title$ = todosStore.pipe(
selectEntityByPredicate(({ completed }) => !completed, {
pluck: 'title',
idKey: '_id',
})
);
const title$ = todosStore.pipe(
selectEntityByPredicate(({ completed }) => !completed, {
pluck: (e) => e.title,
idKey: '_id',
})
);
selectMany
Select multiple entities from the store:
import { selectMany } from '@ngneat/elf-entities';
const todos$ = todosStore.pipe(selectMany([id, id]));
const titles$ = todosStore.pipe(selectMany(id, { pluck: 'title' }));
const titles$ = todosStore.pipe(selectMany(id, { pluck: (e) => e.title }));
selectManyByPredicate
Select multiple entities from the store by predicate:
import { selectManyByPredicate } from '@ngneat/elf-entities';
const todos$ = todosStore.pipe(
selectManyByPredicate((entity) => entity.completed === false)
);
const titles$ = todosStore.pipe(
selectManyByPredicate((entity) => entity.completed === false, {
pluck: 'title',
})
);
const titles$ = todosStore.pipe(
selectManyByPredicate((entity) => entity.completed === false, {
pluck: (e) => e.title,
})
);
selectFirst
Select the first entity from the store:
import { selectFirst } from '@ngneat/elf-entities';
const first$ = todosStore.pipe(selectFirst());
selectLast
Select the last entity from the store:
import { selectLast } from '@ngneat/elf-entities';
const last$ = todosStore.pipe(selectLast());
selectEntitiesCount
Select the store's entity collection size:
import { selectEntitiesCount } from '@ngneat/elf-entities';
const count$ = todosStore.pipe(selectEntitiesCount());
selectEntitiesCountByPredicate
Select the store's entity collection size:
import { selectEntitiesCountByPredicate } from '@ngneat/elf-entities';
const count$ = todosStore.pipe(
selectEntitiesCountByPredicate((entity) => entity.completed)
);
getAllEntities
Get the entity collection:
import { getAllEntities } from '@ngneat/elf-entities';
const todos = todosStore.query(getAllEntities());
getEntitiesIds
Get the entities ids:
import { getEntitiesIds } from '@ngneat/elf-entities';
const todosIds = todosStore.query(getEntitiesIds());
getEntity
Get an entity by id:
import { getEntity } from '@ngneat/elf-entities';
const todo = todosStore.query(getEntity(id));
getEntityByPredicate
Get first entity from the store by predicate:
import { getEntityByPredicate } from '@ngneat/elf-entities';
const todo = todosStore.query(
getEntityByPredicate(({ title }) => title === 'Elf')
);
hasEntity
Returns whether an entity exists:
import { hasEntity } from '@ngneat/elf-entities';
if (todosStore.query(hasEntity(id))) {
}
getEntitiesCount
Get the store's entity collection size:
import { getEntitiesCount } from '@ngneat/elf-entities';
const count = todosStore.query(getEntitiesCount());
getEntitiesCountByPredicate
Get the store's entity collection size:
import { getEntitiesCountByPredicate } from '@ngneat/elf-entities';
const count = todosStore.query(
getEntitiesCountByPredicate((entity) => entity.completed)
);
Mutations
setEntities
Replace current collection with the provided collection:
import { setEntities } from '@ngneat/elf-entities';
todosStore.update(setEntities([todo, todo]));
setEntitiesMap
Replace current collection with the provided map:
import { setEntitiesMap } from '@ngneat/elf-entities';
const todos = {
1: {
id: 1,
task: 'Buy milk',
},
2: {
id: 2,
task: 'Fix car',
},
};
todosStore.update(setEntitiesMap(todos));
addEntities
Add an entity or entities to the store:
import { addEntities } from '@ngneat/elf-entities';
todosStore.update(addEntities(todo));
todosStore.update(addEntities([todo, todo]));
todosStore.update(addEntities([todo, todo], { prepend: true }));
addEntitiesFifo
Add an entity or entities to the store using fifo strategy:
import { addEntitiesFifo } from '@ngneat/elf-entities';
todosStore.update(addEntitiesFifo([entity, entity]), { limit: 3 });
updateEntities
Update an entity or entities in the store:
import { updateEntities } from '@ngneat/elf-entities';
todosStore.update(updateEntities(id, { name }));
todosStore.update(updateEntities(id, (entity) => ({ ...entity, name })));
todosStore.update(updateEntities([id, id, id], { open: true }));
updateEntitiesByPredicate
Update an entity or entities in the store:
import { updateEntitiesByPredicate } from '@ngneat/elf-entities';
todosStore.update(
updateEntitiesByPredicate(({ count }) => count === 0, { open: false })
);
todosStore.update(
updateEntitiesByPredicate(({ count }) => count === 0),
(entity) => ({ ...entity, open: false })
);
updateAllEntities
Update all entities in the store:
import { updateAllEntities } from '@ngneat/elf-entities';
todosStore.update(updateAllEntities({ name: 'elf' }));
todosStore.update(
updateAllEntities((entity) => ({ ...entity, count: entity.count + 1 }))
);
upsertEntities
Add or update entities.
To identify entities in the store, every entity must have an id
property. Any partial entities will be merged with the existing ones:
import { upsertEntitiesBy } from '@ngneat/elf-entities';
todosStore.update(upsertEntities({ id: '1', happy: true }));
todosStore.update(
upsertEntities([
{ id: '1', happy: true },
{ id: '2', name: 'elf 2', happy: false },
])
);
upsertEntitiesById
Insert or update an entity. When the id isn't found, it creates a new entity; otherwise, it performs an update:
import { upsertEntitiesById } from '@ngneat/elf-entities';
const creator = (id) => createTodo(id);
todosStore.update(
upsertEntitiesById(1, {
updater: { name: 'elf' },
creator,
})
);
todosStore.update(
upsertEntitiesById([1, 2], {
updater: (entity) => ({ ...entity, count: entity.count + 1 }),
creator,
})
);
To perform a merge between a new entity and an updater
result, use the mergeUpdaterWithCreator
option:
todosStore.update(
upsertEntitiesById([1, 2], {
updater: (entity) => ({ ...entity, name }),
creator,
mergeUpdaterWithCreator: true,
})
);
The above example will first create the entity using the creator method, then pass the result to the updater method, and merge both.
updateEntitiesIds
Update id of an entity or entities in the store:
import { updateEntitiesIds } from '@ngneat/elf-entities';
todosStore.update(updateEntitiesIds(oldId, newId));
todosStore.update(updateEntitiesIds([oldId1, oldId2], [newId1, newId2]));
The most common use case for this is "optimistic updates":
function addTodo(todo: Todo) {
const tempId = generateRandomId();
todosStore.update(addEntities({ ...todo, id: tempId }));
addTodoToServer(todo).then(
(response) => {
todosStore.update(
updateEntitiesIds(tempId, response.id),
updateEntities(response.id, response)
);
},
(error) => {
todosStore.update(deleteEntities(tempId));
// handle error
}
);
}
deleteEntities
Delete an entity or entities from the store:
import { deleteEntities } from '@ngneat/elf-entities';
todosStore.update(deleteEntities(id));
todosStore.update(deleteEntities([id, id]));
deleteEntitiesByPredicate
Delete an entity or entities from the store:
import { deleteEntitiesByPredicate } from '@ngneat/elf-entities';
todosStore.update(deleteEntitiesByPredicate(({ completed }) => completed));
deleteAllEntities
Delete all entities from the store:
import { deleteAllEntities } from '@ngneat/elf-entities';
todosStore.update(deleteAllEntities());
moveEntity
Moves an entity within the store:
import { moveEntity } from '@ngneat/elf-entities';
todosStore.update(moveEntity({ fromIndex: 0, toIndex: 1 }));
idKey
By default, Elf takes the id
key from the entity id
field. To change it, you can pass the idKey
option to
the withEntities
function:
import { createStore } from '@ngneat/elf';
import { addEntities } from '@ngneat/elf-entities';
interface Todo {
_id: number;
label: string;
}
const todosStore = createStore(
{ name: 'todos' },
withEntities<Todo, '_id'>({ idKey: '_id' })
);
initialValue
In case that you need to start the entities
state with a value, you can specify it in the initialValue
configuration:
import { createStore } from '@ngneat/elf';
const store = createStore(
{ name },
withEntities<Widget>({ initialValue: [{ id: 1, name: '' }] })
);