Entity Store
For the most part, the stores you'll require in your applications will be entity stores. You can think of an entity store as a table in a database, where each table represents a flat collection of entities.
Akita's EntityStore
simplifies the process, giving you everything you need to manage it.
Let's see how we can use it to create a todos
table, i.e., an EntityStore
managing a Todo
object:
import { EntityState, EntityStore, StoreConfig } from '@datorama/akita';
import { Todo } from './todo.model';
export interface TodosState extends EntityState<Todo, number> { }
@StoreConfig({ name: 'todos' })
export class TodosStore extends EntityStore<TodosState> {
constructor() {
super() ;
}
}
First, we need to define the store's interface. In our case, we can make do with extending the EntityState
from Akita, providing it with the Todo
Entity type and the id
type, which in our case is a number
.
EntityState
has the following signature:
export interface EntityState<Entity = any, IDType = any> {
entities: HashMap<Entity>;
ids: IDType[];
loading: boolean;
error: any;
}
Extend the Entity Store
Sometimes it's useful to extend the store and add more properties to it, for example, you might want to save the todos active filter.
To do so, first, we need to update our interface with the property
type:
import { EntityState, EntityStore, StoreConfig } from '@datorama/akita';
import { Todo } from './todo.model';
export interface TodosState extends EntityState<Todo, number> {
filter: string;
}
Next, if we want to pass an initial state for this property, we can do it in the constructor as we did with a Store:
import { EntityState, EntityStore, StoreConfig } from '@datorama/akita';
import { Todo } from './todo.model';
export interface TodosState extends EntityState<Todo, number> {
filter: string;
}
@StoreConfig({ name: 'todos' })
export class TodosStore extends EntityStore<TodosState> {
constructor() {
super({ filter: 'ALL' });
}
}
Now we can update the property by using the update()
method. For example:
store.update({ filter: 'COMPLETED' });
Entity Id
By default, Akita takes the id
key from the entity id
field. To change it, you can pass the idKey
option to the store config:
export interface TodosState extends EntityState<Todo, number> {}
@StoreConfig({ name: 'todos', idKey: '_id' })
export class TodosStore extends EntityStore<TodosState> {
constructor() {
super();
}
}
Nested Entity Id
idKey
can only specify a top-level property of an entity.
In cases an Entity Id is nested in the object, we can use Store Middlware to populate the idKey
field.
export interface Todo {
description: string;
nested: {
id: number;
};
}
export interface TodosState extends EntityState<Todo, number> {}
@StoreConfig({ name: 'todos', idKey: '_id' })
export class TodosStore extends EntityStore<TodosState> {
constructor() {
super();
}
akitaPreAddEntity(newEntity: Todo): Todo & { _id: string; } {
return {
...newEntity,
_id: newEntity.nested.id,
};
}
}
By using this model, you will receive a lot of built-in functionality from Akita:
API
set()
Replace current collection with the provided collection, and resets the active entity:
// Pass an array
store.set([ Entity, Entity ]);
// Pass an object
store.set({ 1: Entity, 2: Entity });
// Set the active id
store.set([ Entity, Entity ], { activeId: 1 });
If you pass an object, Akita doesn't guarantee the order of the data received.
add()
Add an entity or entities to the store:
// Add multiple
store.add([Entity, Entity]);
// Add one
store.add(Entity);
// Prepend
store.add([Entity, Entity], { prepend: true });
store.add(Entity, { prepend: true })
update()
Update an entity or entities in the store:
// Update one
store.update(entityId, { name: 'New Name' });
// Use a predicate function
store.update(({ name }) => name === 'John', {
name: 'New Name'
});
// Use a callback function
store.update(entityId, entity => {
return {
config: {
...entity.config,
date
}
}
});
// Update multiple
store.update([id, id, id], { count: 5 });
// Update all
store.update(null, { name: 'New Name' });
remove()
Remove one or more entities from the store:
// Remove one
store.remove(5);
// Remove multiple
store.remove([1,2,3]);
// Remove by predicate
store.remove(({ name }) => name === 'John');
// Remove all
store.remove();
upsert()
Insert or update an entity. Creates a new entity when no entity matches the id
; otherwise, it performs an update:
store.upsert(id, { isOpen: true }, (id, newState) => ({ id, ...newState });
store.upsert(id, (oldState) => ({ isOpen: !(oldState?.isOpen ?? true) }), (id, newState) => ({ id, ...newState });
The first argument is the entity id to be inserted or updated, the second contains the new entity state, and the last argument is the callback for creating a new entity if it does not exist.
The initializing callback parameter can be omitted, but then type safety of entities cannot be guaranteed. Use
this if all state properties are declared optional or if manually type check for undefined
.
upsertMany()
Insert or update multiple entities. Creates a new entity when no entity matches the id
; otherwise, it performs an update:
store.upsertMany([entity, entity]);
replace()
Replace an entity (except the id
):
store.replace(id, newEntity);
store.replace([id, id, id], newEntity);
move()
Move an entity from the current index
to a new index
:
store.move(fromIndex, toIndex);
setLoading()
Update the store's loading
state. The initial value is set to true
and is switched to false
when you call store.set()
. This can come in handy for indicating loading
:
store.setLoading(true);
setError()
Update the store's error
state:
store.setError(error);