Skip to main content

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: '' }] })
);