Skip to content

Latest commit

 

History

History
269 lines (183 loc) · 11.8 KB

entity-actions.md

File metadata and controls

269 lines (183 loc) · 11.8 KB

Entity Actions

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.

Action and EntityAction

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 the tag and the op
  • entityName - the name of the entity type
  • op - the name of an entity operation
  • payload? - the message data for the action.
  • tag? - the tag to use within the generated type. If not specified, the entityName 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.

EntityAction consumers

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.

Creating an EntityAction

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's formatActionType() method to produce the Action.type string.

Because ngrx-data ignores the type, you can replace formatActionType() with your own method if you prefer a different format or provide and inject your own EntityActionFactory.

Note that each entity type has its own _unique Action for each operation_, as if you had created them individually by hand.

Tagging the EntityAction

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

Handcrafted EntityAction

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

Where are the EntityActions?

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.

Why this matters

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.

EntityAction.error

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.

EntityAction.skip

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.

EntityCache-level actions

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.