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
entityTypeproperty. It treats any action with anentityTypeproperty as anEntityAction.
The entity reducer's primary job is to
- extract the
EntityCollectionfor the action's entity type from thestate. - create a new, initialized entity collection if necessary.
- get or create the
EntityCollectionReducerfor that entity type. - call the entity collection reducer with the collection and the action.
- replace the entity collection in the
EntityCachewith 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.tsfor 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_CACHEreplaces 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.tsfor 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
EntityCollectionfor 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.tsfor examples.