Skip to content

Latest commit

 

History

History
186 lines (128 loc) · 8.8 KB

entity-reducer.md

File metadata and controls

186 lines (128 loc) · 8.8 KB

Entity Reducer

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 an entityType property as an EntityAction.

The entity reducer's primary job is to

  • extract the EntityCollection for the action's entity type from the state.
  • 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.

EntityCollectionReducers

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.

Register custom reducers

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.

Default EntityCollectionReducer

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.

Initializing collection state

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.

Customizing entity reducer behavior

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.

Entity Collection MetaReducers

An entity collection MetaReducer takes an entity collection reducer as an 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:

  1. always returns an EntityCollection for the same entity.
  2. return synchronously (no waiting for server responses).
  3. never mutate the original action; clone it to change it.

Compared to @ngrx/store/MetaReducers

The entity collection MetaReducer is modeled on the @ngrx/store/MetaReducer ("store MetaReducer") but is crucially different in several respects.

The store MetaReducer broadly targets store reducers. It wraps all store reducers, sees all actions, and can update any state in the store. But a store MetaReducer can neither see nor wrap an entity collection reducer.

An entity collection MetaReducer is narrowly focused on manipulation of a single, target entity collection. It wraps all entity collection reducers.

But it can't wrap store reducers and the new reducer it produces can't access other collections,the entity cache, or any other state in the store.

Provide Entity MetaReducers to the factory

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.

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.

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 EntityService.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.