The EntityCollectionService
dispatches an EntityAction
to the ngrx store when you call one of its commands to query or update entities in a cached collection.
A vanilla
ngrx Action
is a message.
The message describes an operation that can change state in the store.
The action's type
identifies the operation.
It's optional payload
carries the message data necessary to perform the operation.
An EntityAction
is a super-set of the ngrx Action
.
It has additional properties that guide ngrx-data's handling of the action. Here's the full interface.
export interface EntityAction<P = any> extends Action {
readonly type: string;
readonly entityName: string;
readonly op: EntityOp;
readonly payload?: P;
readonly tag?: string;
error?: Error;
skip?: boolean
}
type
- action name, typically generated from thetag
and theop
entityName
- the name of the entity typeop
- the name of an entity operationpayload?
- the message data for the action.tag?
- the tag to use within the generated type. If not specified, theentityName
is the tag.error?
- an unexpected action processing error.skip?
- true if downstream consumers should skip processing the action.
The type
is the only property required by ngrx. It is a string that uniquely identifies the action among the set of all the types of actions that can be dispatched to the store.
Ngrx-data doesn't care about the type
. It pays attention to the entityName
and op
properties.
The entityName
is the name of the entity type.
It identifies the entity collection in the ngrx-data cache to which this action applies.
This name corresponds to ngrx-data metadata for that collection.
An entity interface or class name, such as 'Hero'
, is a typical entityName
.
The op
identifies the operation to perform on the entity collection. Ngrx-data recognizes names in the EntityOp
enumeration.
Each of these EntityOp
names corresponds to one of almost forty operations
that the ngrx-data library can perform.
The payload
is conceptually the body of the message.
Its type and content should fit the requirements of the operation to be performed.
The optional tag
appears in the generated type
text when the EntityActionFactory
creates this EntityAction
.
The entityName
is the default tag that appears between brackets in the formatted type
,
e.g., '[Hero] ngrx-data/query-all'
.
The error
property indicates that something went wrong while processing the action. See more below.
The skip
property tells downstream action receivers that they should skip the usual action processing.
This flag is usually missing and is implicitly false.
See more below.
The ngrx-data library ignores the Action.type
.
All ngrx-data library behaviors are determined by the entityName
and op
properties alone.
The ngrx-data EntityReducer
redirects an action to an EntityCollectionReducer
based on the entityName
and that reducer processes the action based on the op
.
The EntityEffects
intercepts an action if its op
is among the small set of persistence EntityAction.op
names.
The effect picks the right data service for that action's entityName
, then tells the service to make the appropriate HTTP request and handle the response.
You can create an EntityAction
by hand if you wish.
The ngrx-data library considers any action with an entityName
and op
properties to be an EntityAction
.
The EntityActionFactory.create()
method helps you create a consistently well-formed EntityAction
instance
whose type
is a string composed from the tag
(the entityName
by default) and the op
.
For example, the default generated Action.type
for the operation that queries the server for all heroes is '[Hero] ngrx-data/query-all'
.
The
EntityActionFactory.create()
method calls the factory'sformatActionType()
method to produce theAction.type
string.Because ngrx-data ignores the
type
, you can replaceformatActionType()
with your own method if you prefer a different format or provide and inject your ownEntityActionFactory
.
Note that each entity type has its own _unique Action
for each operation_, as if you had created them individually by hand.
A well-formed action type
can tell the reader what changed and
who changed it.
The ngrx-data library doesn't look at the type of an EntityAction
,
only its entityName
and entityOp
.
So you can get the same behavior from several different actions,
each with its own informative type
, as long as they share the same
entityName
and entityOp
.
The optional tag
parameter of the EntityActionFactory.create()
method makes
it easy to produce meaningful EntityActions.
You don't have to specify a tag. The entityName
is the default tag that appears between brackets in the formatted type
,
e.g., '[Hero] ngrx-data/query-all'
.
Here's an example that uses the injectable EntityActionFactory
to construct the default "query all heroes" action.
const action = entityActionFactory<Hero>(
'Hero', EntityOp.QUERY_ALL, null, 'Load Heroes On Start'
);
store.dispatch(action);
Thanks to the ngrx-data effects, this produces two actions in the log, the first to initiate the request and the second with the successful response:
[Hero] ngrx-data/query-all
[Hero] ngrx-data/query-all-success
This default entityName
tag identifies the action's target entity collection.
But you can't understand the context of the action from these log entries. You don't know who dispatched the action or why.
The action type
is too generic.
You can create a more informative action by providing a tag that better describes what is happening and also make it easier to find where that action is dispatched by your code.
For example,
const action = entityActionFactory<Hero>(
'Hero', EntityOp.QUERY_ALL, null, 'Load Heroes On Start'
);
store.dispatch(action);
The action log now looks like this:
[Load Heroes On Start] ngrx-data/query-all
[Load Heroes On Start] ngrx-data/query-all-success
You don't have to create entity actions with the EntityActionFactory
.
Any action object with an entityName
and op
property is
an entity action, as explained below.
The following example creates the initiating "query all heroes" action by hand.
const action = {
type: 'some/arbitrary/action/type',
entityName: 'Hero',
op: EntityOp.QUERY_ALL
};
store.dispatch(action);
It triggers the HTTP request via ngrx-data effects, as in the previous examples.
Just be aware that ngrx-data effects uses the EntityActionFactory
to create the second, success Action.
Without the tag
property, it produces a generic success action.
The log of the two action types will look like this:
some/arbitrary/action/type
[Hero] ngrx-data/query-all-success
In an ngrx-data app, the ngrx-data library creates and dispatches EntityActions for you.
EntityActions are largely invisible when you call the EntityCollectionService
API.
You can see them in action with the
ngrx store dev-tools.
In an ordinary ngrx application, you hand-code every Action
for every state in the store
as well as the reducers
that process those actions.
It takes many actions, a complex reducer, and the help of the @ngrx/effects package to manage queries and saves for a single entity type.
The @ngrx/entity package makes the job considerably easier.
The ngrx-data library internally delegates much of the heavy lifting to @ngrx/entity.
But you must still write a lot of code for each entity type. You're expected to create eight actions per entity type and write a reducer that responds to these eight actions by calling eight methods of an @ngrx/entity EntityAdapter.
These artifacts only address the cached entity collection.
You may write as many as eighteen additional actions to support a typical complement of asynchronous CRUD (Create, Retrieve, Update, Delete) operations. You'll have to dispatch them to the store where you'll process them with more reducer methods and effects that you must also hand code.
With vanilla ngrx, you'll go through this exercise for every entity type. That's a lot of code to write, test, and maintain.
With the help of ngrx-data, you don't write any of it. Ngrx-data creates the actions and the dispatchers, reducers, and effects that respond to those actions.
The presence of an EntityAction.error
property indicates that something bad happened while processing the action.
An EntityAction
should be immutable. The EntityAction.error
property is the only exception and is strictly an internal property of the ngrx-data system.
You should rarely (if ever) set it yourself.
The primary use case for error
is to catch reducer exceptions.
Ngrx stops subscribing to reducers if one of them throws an exception.
Catching reducer exceptions allows the application to continue operating.
Ngrx-data traps an error thrown by an EntityCollectionReducer
and sets the EntityAction.error
property to the caught error object.
The error
property is important when the errant action is a persistence action (such as SAVE_ADD_ONE
).
The EntityEffects
will see that such an action has an error and will return the corresponding failure action (SAVE_ADD_ONE_ERROR
) immediately, without attempting an HTTP request.
This is the only way we've found to prevent a bad action from getting through the effect and triggering an HTTP request.
The skip
property tells downstream action receivers that they should skip the usual action processing.
This flag is usually missing and is implicitly false.
The ngrx-data sets skip=true
when you try to delete a new entity that has not been saved.
When the EntityEffects.persist$
method sees this flag set true on the EntityAction
envelope,
it skips the HTTP request and dispatches an appropriate _SUCCESS
action with the
original request payload.
This feature allows ngrx-data to avoid making a DELETE request when you try to delete an entity that has been added to the collection but not saved. Such a request would have failed on the server because there is no such entity to delete.
See the EntityChangeTracker
page for more about change tracking.
A few actions target the entity cache as a whole.
SET_ENTITY_CACHE
replaces the entire cache with the object in the action payload,
effectively re-initializing the entity cache to a known state.
MERGE_ENTITY_CACHE
replaces specific entity collections in the current entity cache
with those collections present in the action payload.
It leaves the other current collections alone.
Learn about them in the "EntityReducer" document.