Skip to content

Latest commit

 

History

History
245 lines (160 loc) · 11 KB

entity-reducer.md

File metadata and controls

245 lines (160 loc) · 11 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.

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

MetaReducers

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

  1. EntityCache MetaReducer, scoped to the entire entity cache
  2. EntityCollection MetaReducer, scoped to a particular collection.

Entity Cache MetaReducers

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:

  1. always returns the entire entity cache.
  2. return synchronously (no waiting for server responses).
  3. 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.

Entity Collection MetaReducers

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:

  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

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.

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.

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.