The Entity Reducer is the master reducer for all entity collections in the stored entity cache.
The library doesn't have a named entity reducer type.
Rather it relies on the EntityReducerFactory.create()
method to produce that reducer,
which is an ngrx ActionReducer<EntityCache, EntityAction>
.
Such a reducer function takes an EntityCache
state and an EntityAction
action
and returns an EntityCache
state.
The reducer responds either to an EntityCache-level action (rare)
or to an EntityAction
targeting an entity collection (the usual case).
All other kinds of Action
are ignored and the reducer simply returns the given state
.
The reducer filters specifically for the action's
entityType
property. It treats any action with anentityType
property as anEntityAction
.
The entity reducer's primary job is to
- extract the
EntityCollection
for the action's entity type from thestate
. - create a new, initialized entity collection if necessary.
- get or create the
EntityCollectionReducer
for that entity type. - call the entity collection reducer with the collection and the action.
- replace the entity collection in the
EntityCache
with the new collection returned by the entity collection reducer.
An EntityCollectionReducer
applies actions to an EntityCollection
in the EntityCache
held in the ngrx store.
There is always a reducer for a given entity type.
The EntityReducerFactory
maintains a registry of them.
If it can't find a reducer for the entity type, it creates one, with the help
of the injected EntityCollectionReducerFactory
, and registers that reducer
so it can use it again next time.
You can create a custom reducer for an entity type and
register it directly with EntityReducerFactory.registerReducer()
.
You can register several custom reducers at the same time
by calling EntityReducerFactory.registerReducer(reducerMap)
where
the reducerMap
is a hash of reducers, keyed by entity-type-name.
The EntityCollectionReducerFactory
creates a default reducer that leverages the capabilities of the @ngrx/entity/EntityAdapter
,
guided by the app's entity metadata.
The default reducer decides what to do based on the EntityAction.op
property,whose string value it expects will be a member of the
EntityOp
enum.
Many of the EntityOp
values are ignored; the reducer simply returns the
entity collection as given.
Certain persistence-oriented ops, for example,
are meant to be handled by the ngrx-data persist$
effect.
They don't update the collection data (other than, perhaps, to flip the loading
flag).
Others add, update, and remove entities from the collection.
Remember that immutable objects are a core principle of the redux/ngrx pattern. These reducers don't actually change the original collection or any of the objects in it. They make a copy of the collection and only update copies of the objects within the collection.
See the @ngrx/entity/EntityAdapter collection methods for a basic guide to the cache altering operations performed by the default entity collection reducer.
The EntityCollectionReducerFactory
and its tests are the authority on how the default reducer actually works.
The NgrxDataModule
adds an empty EntityCache
to the ngrx-data store.
There are no collections in this cache.
If the master entity reducer can't find a collection for the action's entity type,
it creates a new, initialized collection with the help of the
EntityCollectionCreator
, which was
injected into the EntityReducerFactory
.
The creator returns an initialized collection from the initialState
in the entity's
EntityDefinition
.
If the entity type doesn't have a definition or the definition doesn't have an initialState
property value,
the creator returns an EntityCollection
.
The entity reducer then passes the new collection in the state
argument of the entity collection reducer.
You can replace any entity collection reducer by registering a custom alternative.
You can replace the default entity reducer by
providing a custom alternative to the EntityCollectionReducerFactory
.
You could even replace the master entity reducer by
providing a custom alternative to the EntityReducerFactory
.
But quite often you'd like to extend a collection reducer with some additional reducer logic that runs before or after.
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.
See
entity-reducer.spec.ts
for examples of these actions.
These actions might be part of your plan to support offline scenarios or rollback changes to many collections at the same time.
For example, you could subscribe to the EntityServices.entityCache$
selector.
When the cache changes, you could
serialize the cache to browser local storage.
You might want to debounce for a few seconds to reduce churn.
Later, when relaunching the application, you could dispatch the SET_ENTITY_CACHE
action to initialize the entity-cache even while disconnected.
Or you could dispatch the MERGE_ENTITY_CACHE
to rollback selected collections to a known state as
in error-recovery or "what-if" scenarios.
Important:
MERGE_ENTITY_CACHE
replaces the currently cached collections with the entity collections in its payload. It does not merge the payload collection entities into the existing collections as the name might imply. May reconsider and do that in the future.
If you want to create and reduce additional, cache-wide actions, consider the EntityCache MetaReducer, described in the next section.
The ngrx/store
supports MetaReducers that can inspect and process actions flowing through the store and potentially change state in the store.
A MetaReducer is a function that takes a reducer and returns a reducer. Ngrx composes these reducers with other reducers in a chain of responsibility.
Ngrx calls the reducer returned by a MetaReducer just as it does any reducer. It calls it with a state object and an action.
The MetaReducer can do what it wants with the state and action. It can log the action, handle the action on its own, delegate to the incoming reducer, post-process the updated state, or all of the above.
Remember that the actions themselves are immutable. Do not change the action!
Like every reducer, the state passed to a MetaReducer's reducer is only the section of the store that is within the reducer's scope.
Ngrx-data supports two levels of MetaReducer
- EntityCache MetaReducer, scoped to the entire entity cache
- EntityCollection MetaReducer, scoped to a particular collection.
The EntityCache MetaReducer helps you inspect and apply actions that affect the entire entity cache. You might add custom actions and an EntityCache MetaReducer to update several collections at the same time.
An EntityCache MetaReducer reducer must satisfy three requirements:
- always returns the entire entity cache.
- return synchronously (no waiting for server responses).
- never mutate the original action; clone it to change it.
We intend to explain how in a documentation update. For now, see the
ngrx-data.module.spec.ts
for examples.
An entity collection MetaReducer takes an entity collection reducer as its reducer argument and returns a new entity collection reducer.
The new reducer receives the EntityCollection
and EntityAction
arguments that would have gone to the original reducer.
It can do what it wants with those arguments, such as:
- log the action,
- transform the action into a different action (for the same entity collection),
- call the original reducer,
- post-process the results from original reducer.
The new entity collection reducer must satisfy three requirements:
- always returns an
EntityCollection
for the same entity. - return synchronously (no waiting for server responses).
- never mutate the original action; clone it to change it.
While the entity collection MetaReducer is modeled on the @ngrx/store/MetaReducer
("store MetaReducer"), it is crucially different in several respects.
The store MetaReducer broadly targets store reducers. It wraps store reducers, sees all actions, and can update any state within its scope.
But a store MetaReducer neither see nor wrap an entity collection reducer. These entity collection reducers are internal to the EntityCache Reducer that is registered with the ngrx-data feature.
An entity collection MetaReducer is narrowly focused on manipulation of a single, target entity collection. It wraps all entity collection reducers.
Note that it can't can't access other collections,the entity cache, or any other state in the store. If you need a cross-collection, MetaReducer, try the EntityCache MetaReducer described above.
Create one or more entity collection MetaReducers and add them to an array.
Provide this array with the ENTITY_COLLECTION_META_REDUCERS
injection token
where you import the NgrxDataModule
.
The EntityReducerFactory
injects it and composes the
array of MetaReducers into a single meta-MetaReducer.
The earlier MetaReducers wrap the later ones in the array.
When the factory register an EntityCollectionReducer
, including the reducers it creates,
it wraps that reducer in the meta-MetaReducer before
adding it to its registry.
All EntityActions
dispatched to the store pass through this wrapper on their way in and out of the entity-specific reducers.
We intend to explain how to create and provide entity collection MetaReducers in a documentation update. For now, see the
entity-reducer.spec.ts
for examples.