Core to NgRx Auto-Entity is it's internal state structure for each entity. You may have noticed the little IEntityState<TModel>
interface floating around prior documentation, including the Quick Start. This interface is much like the EntityState<T>
interface from @ngrx/entity, although more complex as Auto-Entity manages more state for you.
While our internal types are named slightly differently, the structure of our IEntityState
interface is name-compatible with the structure @ngrx/entity expects. As such, with perhaps a little type coercion when necessary, it should be possible to utilize @ngrx/entity functionality such as its adapter to update Auto-Entity state...if the need ever arose.
You may notice that we track a lot of additional but optional state as well. This includes the current entity, information about the current page or range loaded into state, as well as various flags and timestamps.
This interface is fundamental to all entities who's state is managed by NgRx Auto-Entity. You can and should use this interface wherever a state interface is required, such as root or feature state interfaces that will be used in a reducer map.
Learn this interface if you intend to leverage any of the lower level capabilities of NgRx Auto-Entity. In particular, if you ever provide extra initial state, make sure you know what properties are involved in the event you wish to ADD new state information, or SET existing state information with initial values.
Another lower level element returned by buildState
is the selector map. Much like @ngrx/entity, the selector map is a simple object (associative array) that maps common names to selector functions.
As with the IEntityState
interface, we have attempted to maintain naming compatibility with @ngrx/entity here, however also as with IEntityState
we provide a lot more than @ngrx/entity does as well. Selectors are available for every state property managed by NgRx Auto-Entity.
Boilerplate Encapsulated
The buildState
function has a fairly strait forward and simple signature that requires only two arguments: the model type of the entity you wish to build NgRx state for, and an optional object containing extra initial state:
You may have noticed that there is a fairly complex generic signature here. This function returns an object that contains all the necessary bits and pieces for you to export selectors, create your own customer selectors, provide initial state to your stub reducer, and of course the facade base class.
All of these types tacked onto this function ensure that each of these bits and pieces have the correct types to integrate properly with NgRx down the line.
The first argument of the function is the model type. In this case, you actually pass in the class itself, not an instance of the class. The second argument is an object that contains any extra initial state you wish to include in addition to the initial state generated for you by the library. The signature for the model class is as follows:
As indicated by this interface, all model types must be newable, thus enforcing the class rather than interface requirement for all entity models. Also note that the constructor signature is empty...all models must have an parameterless constructor.
The object returned by buildState
is defined by the IModelState
interface. This interface encapsulates the initialState
, selectors
map, and facade
, among other things:
A word of warning. While the buildState
function does return a prefabricated stub reducer for you, this was a convenience that is sadly not meant to be for any AOT build. All reducer functions must be usable within a decorator, @NgModel,
as the reducer map into which all reducer functions go is referenced in StoreModule.forRoot(...)
when imported into an angular module.
The stub reducer returned by buildState
can be useful for rapid prototyping and initial development, but once you perform a production build or any other build with AOT enabled, you will need to resort to creating your own stub reducer.
Lazy Boilerplate...
The lazy loaded feature module counterpart to buildState
is, of course, buildFeatureState
. This function is similar to buildState
, however it is not identical and has one nuanced change:
Two additional arguments must be passed. The second is the name of the feature state, the same name you specify in the createFeatureSelector
call. The third argument that must be passed to buildFeatureState
is the feature selector for the feature state. The use case would be as follows:
Building feature state otherwise works the same as the standard buildState
function does. It also provides the entityState for the feature entity, a stub reducer and a facade class.
While the general functionality of buildFeatureState works properly at a high level, there is currently a bug in NgRx Auto-Entity that prevents it from finding the proper Injector for lazy-loaded features. Each lazy feature contains its own angular injector instance as well, a child injector that will fall back on the root app injector as necessary. Until this bug is resolved, lazy loaded feature state will not properly function. Our sincerest apologies!
This issue is resolved with v0.2 of NgRx Auto-Entity. You should be able to build feature state with models and entity services encapsulated within lazy loaded modules! Please notify us in our GitHub issues if you encounter any problems with this feature. It is surprisingly complex!
When using feature state, there are some nuanced differences that must be taken into account. With NgRx feature state becomes a new state property on the root state, with the name given to the createFeatureState
function. This new root state property then encapsulates all other state properties for that particular feature.
Due to these differences, it becomes very important to specify the proper name for both the createFeatureState
and the buildFeatureState
function calls. Hence the use of a constant in the prior example.
In addition to this little nuance, there is another critical change that must be made. Where root state may easily share the state interface and reducer map in a single file, such as app.state.ts
, due to the fact that buildFeatureState
depends on the state created by createFeatureState
, but the reducer map depends on the reducer that you create with the initialState
returned by buildFeatureState
, putting the state and the reducer in a single file results in a circular reference problem.
The solution is to simply put the feature state interface and createFeatureState
call in one file, say feature.state.ts
, and put the reducer map in another, say feature.reducer.ts
. This breaks the chain and eliminates the cycle.
Initialization Simplified
At the core of NgRx Auto-Entity is the buildState
function. This utility function encapsulates all the "boilerplate" that you would normally implement yourself when building up state for a new entity. Where in the past you may have manually created an @ngrx/entity adapter, built out your standardized reducer function, and exported a bunch of support elements like initial state, selectors, a key selection handler, etc.
Now you simply need to call buildState()
like so:
That is about as simple as it gets, unless you need to do anything custom, such as add your own additional support actions, use selectors, or retrieve the prefabricated facade base class so you can keep your state encapsulated.
The buildState
function provides all of the functionality necessary to take control when you need to, without being complex, and all while still handling most of the work for you. The full expression of a buildState
call would be as follows:
In addition to providing the initial state version for your stub reducer, the buildState()
function also provides a selectors
map, an entityState
selector to support the creation of custom selectors, a base facade
class (source of ultimate power and simplicity!), and finally a ready-made stub reducer
(only works with non-AOT builds, sorry!)