Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Customers and Orders
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Simplifying Reactive State!
NgRx Auto-Entity is a library that simplifies reactive state in @ngrx
with reusable generic actions, automated effects & reducers and prefabricated facades.
NgRX Auto-Entity is an add-on library for @ngrx
that aims to greatly simplify use of @ngrx
and reduce the implementation load of adding @ngrx
to your application. We provide a set of ready-made, generic actions that cover most of the common use cases such as loading entities and sets of entities as well as creating, updating or replacing, and deleting entities.
Auto-Entity wires in ready-made effects to handle standard behaviors, as well as provides a core meta reducer that reduces all entity state managed by this library. For most use cases, the implementation burden for you the developer will be limited to basic initial @ngrx
setup, creation of models for each entity & creation of services for each entity. This is functionality that would need to be implemented by you the developer regardless.
Where we save you time is by eliminating the need to implement unique sets of CRUD actions for each and every entity, as well as the side effects that handle standard CRUD behavior for each and every entity. This can be a significant amount of development effort for each @ngrx
application, and we hope the savings we offer will allow you to focus on solving the critical business needs of your customers.
Further, Auto-Entity generates pre-fabricated Facades around your entity state, allowing you to extract all @ngrx
and other state related code from your components, encapsulate them in a facade layer that presents a much simpler, more logical and easier to comprehend and use API for your entities.
Create application state interface.
As with any NgRX application, we need to create a new app.state.ts file. We recommend creating is file in a root state directory, located at src/app/state. Import the IEntityState
interface from @briebug/ngrx-auto-entity as well, as we will be using it later.
Define the AppState
interface as normal. We will eventually define each entity state property here. For now, we've left it blank, and will fill it in later on in this quick start.
Remember to export the AppState
type so that it may be used throughout the rest of your app.
Encapsulate!
In our CustomerComponent
, there are a few methods that exhibit "class envy" which is a kind of anti-pattern. For those not familiar with the concept, class envy occurs when methods of one class are more dependent on functionality within another class. It then becomes prudent to move the method into the class of envy, and if necessary parameterize any input required from the method's previous home.
Our CustomerComponent
has two potential candidates for encapsulation within our CustomerFacade
class: hasCustomer
and onSave. We can easily move this functionality into our facade class and make these behaviors reusable in any component that may require interaction with customer entity state:
Entity facades include a considerable amount of ready-to-go functionality. Check out the advanced facade documentation here to learn more about everything our facades provide and how to extend them.
That junk don't belong here!
Once you have enhanced your facade with functionality that belongs in the facade and not the component, it's time to clean up your component. Using the new functionality we have implemented in our customer facade, our component can become reduced to a simpler form:
Create your entity models.
In a departure from classic @ngrx/entity models, each model in your application should be defined as a class (see note below). Here is an example of a Customer
model:
Next we need to import the Key
and Entity
decorators. The Key
decorator is used to specify the property in your model that is the unique identifier. Decorate the id
property, which is the unique identifier for Customer
model. Read more about entity keys in the advanced documentation.
The Entity
decorator is used to attach metadata to your entity model that the NgRx Auto-Entity library can use to perform many of its automated tasks. In version 0.5 of the library, only the modelName
must be specified. Read more about the entity decorator in the advanced documentation.
Note that the model must be a class and not an interface. This is because interfaces are a compile-time only feature of TypeScript, while classes are a native runtime construct in JavaScript. Further, the modelName
must be defined on the entity, as this is the name that the library will use at runtime for minified/uglified code (critical, read more in advanced documentation.)
Define the initial entity state
In our example we are building the state for the Customer
entity. As such, we've created a new customer.state.ts file located at src/app/state/customer.state.ts.
Import the buildState
function from the ngrx-auto-entity module. This function builds the initial state and selectors for each entity. Call the function by passing in the Customer entity class (note, the class must be passed in!) We use object destructuring on the return type access the initialState
, selectors
and facade
base class from the result of buildState
.
We can now further destructure the selectors object to map each type of standard selector to model-specific names for import into other modules:
selectAll
selects the Array
of entities
selectEntities
selects the Dictionary
of entities
selectIds
selects the Array
of entity identifiers (keys)
selectTotal
selects the number
of entities
Note that retrieving and exporting the selectors are optional if you extract the facade. The facade base class generated by buildState fully encapsulates all of the functionality you will need to interact with your entity state, and we generally recommend only using the facade. Demonstration of how to access selectors directly, such as in the event that you may need to create your own custom selectors, is simply for completeness here.
There are many additional selectors bundled as part of each custom entity state built by buildState
that may be mapped to state-specific names. Read more in the advanced documentation.
When the selectors object is destructured we alias the selectors with entity-friendly names to avoid naming conflicts with other exported names. This prevents the need to import entire files with an import * as fromBlah from 'blah'
syntax. Uniquely named exports are enough, and allow selective import into each area of the app.
Finally, we define the customerReducer
function. The reducer function accepts two arguments: state
, which defaults to the initialCustomerState
, and the action
.
A reducer function is necessary to configure the NgRX standard actionsReducer
we defined earlier. For most entities, you will not need to do anything other than return the state passed in, as the autoEntityMetaReducer
will handle reduction for you. If custom reduction is required for your apps, it may be handled in these reducers.
Create service for handling data interactions with server
In our example we are creating a service for persisting entities via a simple REST API. As such, we've created a new entity.service.ts file and defined an injectable EntityService
class.
It's important that each entity service implement the IAutoEntity
interface. This interface supports the following methods:
load()
loadAll()
loadMany()
loadPage()
loadRange()
create()
createMany()
update()
updateMany()
replace()
replaceMany()
delete()
deleteMany()
These methods perform the CRUD operators for entity retrieval and persistence.
To create an entity service, we must import the IAutoEntityService
and IEntityInfo
interfaces. The entity service must implement the IAutoEntityService
interface. The IEntityInfo
object provides metadata about the entities, which can be used to help build urls if necessary.
Finally, we implement each of the necessary methods for retrieving and persisting an entities.
Your implementation may vary based on the method of persistence and the architecture of your API. Each method is optional, and may be implemented on an as-needed basis for each entity. We provide several options for loading data, as well as options for updating (PATCH) or replacing (PUT) entities. Each method of an entity service also provides additional input parameters such as custom criteria. Implement and use what you need.
Finally, in order for NgRx Auto-Entity to find the entity service you just created, you must provide it in your application state. Providing entity services is slightly different than a normal provider, which simply provides itself as the service class.
Here, we have provided the model type as the provider, and specified the EntityService
class as the actual service class via useClass
. This is the simplest model for using Auto-Entity, and for simple backend APIs that follow a common pattern, a single service like this may be reused for any number of entities.
provide: Model, useClass: EntityService
In the event that you have a more complex backend API, or even must integrate with many backend APIs, you may create custom entity services for each entity if necessary, and provide each model with its own unique service class following the same pattern as above.
Finally, import the StateModule
we created earlier into your root AppModule
to bring in all of your root state, including NgRx Auto-Entity.
And, with that, you are done! You can now start using your entity in your app.
Learn about the required paradigm changes for Auto-Entity
For NgRX Auto-Entity to function properly, some minor changes will be required in the way you implement a couple standard bits of code. This includes service implementations and model implementations. These changes are not particularly significant, and for services we provide a lot of meta information to assist you in achieving whatever is necessary for your applications.
As models and services are standard elements of any Angular application, there should be no additional boilerplate code to implement here. These would be implemented regardless. However, be aware of the nuanced changes in how the model and service are used, and the explicit interface requirement for the service.
More significant changes occur when utilizing facades to interact with state. Facades encapsulate the complexities of @ngrx, presenting you with a simplified, logical and more standard API into working with your stateful data.
Own your app and do what you need, when you need
While we strive to provide as much commonly implemented functionality as we can, there can always be cases where you, the developer, must take control. Since this library is implemented much like any standard @ngrx application, it allows you to inject your own behavior whenever you need to.
If you require custom behavior for anything, you are always free to implement your own custom actions & custom effects. You are also free to hook into our library and framework by dispatching our actions from your own actions.
This could, for example, allow you to implement a custom search behavior with your own SearchEntityWhatever
action and searchEntityWhatever$
effect, and dispatch the Auto-Entity relevant success and failure actions on response, which will be handled by our meta reducer. This would integrate a custom initiating action and data from a custom service call into Auto-Entity managed state.
It is possible to integrate custom actions and effects in a variety of ways, allowing you to leverage the lower level power of NgRx when necessary, while still leveraging the ease of use offered by NgRx Auto-Entity.
Understand changes to model implementation
With NgRX Auto-Entity, a very small change must be made to how models are implemented. Standard @ngrx models are implemented as interfaces. A normal model used with @ngrx/entity may look like this:
This simple model would then be wired into @ngrx/entity using the adapter, with a selectId handler to allow entity framework to determine what the primary key of the entity is.
Due to the dynamic nature of how NgRX Auto-Entity works, interfaces are insufficient as they are compile-time only types in TypeScript. All of their information is ultimately stripped from the transpiled and optimized JavaScript code that you will ultimately release to a web server. To combat this issue, we must convert our entities to classes, which are a native runtime feature of JavaScript.
In addition to converting our model from an interface to a class, notice that we have also imported Key
from @briebug/ngrx-auto-entity
and decorated our id
property with @Key
. This is how the Auto-Entity framework is able to determine which property of the model represents the primary key. As with @ngrx/entity, this key is used to organize and look up entities stored in state.
Note that Auto-Entity supports composite keys. Simply decorate each property that participates in a primary key with the @Key
decorator if necessary.
A simple but common example of a composite key might be something along the lines of an order line item, which references both an order and a product along with a quantity:
For more detail about how composite keys work, read the advanced usage documentation on models and keys.
You should also notice that we have decorated our entity with the Entity
decorator, imported from @briebug/ngrx-auto-entity
. This decorator allows us to associate important metadata with each of our entities. Currently, the only required metadata is the modelName
which allows us to define a minification/uglification-safe identifier for our entity models.
The Entity
decorator allows the definition of other metadata for our entities, including alternative names for specific use cases, a sort comparer, data transformations, and other things. For more detail about entity metadata, read the advanced usage documentation on models.
Understand the changes to defining service providers
For most Angular applications, services can be provided very simply, simply by including the class in the array of providers
in your module. With NgRX Auto-Entity, due to its dynamic nature we must change how services for entities are provided a little bit.
Instead of registering the service itself directly, we must instead register the model class as the provider, and map it to an entity service via the useClass
option. Like so:
When dispatching an action, the model class is specified as the first parameter, and as such the model class is the only thing NgRX Auto-Entity can use to look up the necessary service provider. By mapping the model class to the service class, you are leveraging a standard Angular paradigm to support dynamic service lookup at runtime.
Auto-Entity makes it possible to reuse a single smart entity service with multiple entities. This approach allows another reduction in developer effort, requiring an initial effort up front to implement the entity service, however over the lifetime of the application that single service may be used countless times for countless entities.
Mapping each entity to a service may not be the most efficient approach. Each provider will be a new instance of the entity service. In order to supply a single service instance you can use a factory with another provider.
To ensure that auto-entity's service lookup for a given identifier of application state will work even when your code has been minified/uglified, run through AoT, optimized, etc. we require that a string name for each model be defined with the Entity
decorator. This name is used instead of the actual runtime class name, thus guaranteeing that even if the class name (and therefor constructor.name) is changed during minification, auto-entity will still function properly.
NgRx Auto-Entity will still attempt to utilize the model class's constructor.name in the event that a model has not been decorated with the @Entity decorator. This can allow simple, rapid prototyping of an auto-entity application when run in development mode. However without proper decoration, minified, optimized, aot production code will not function properly.
A future version of NgRx Auto-Entity may change this behavior in favor of requiring that every entity always be decorated with @Entity. As such, it is encouraged that all entity models always be properly decorated at all times.
Understand changes to service implementation
With a normal @ngrx application, you are free to implement services however you see fit. There are no explicit requirements by @ngrx, since you are also on the hook to implement the effects as well, and it is your own effects that call your services.
NgRX Auto-Entities dynamic nature requires that data services implement the IAutoEntityService<TModel>
interface. This interface defines the contract by which Auto-Entity interacts with your services. This interface is described as follows:
This interface defines a different method that corresponds to each different kind of initiating generic action provided as a part of the NgRX Auto-Entity library. Every method provides entityInfo
as the first parameter, which contains metadata about the entity, including its name (based on the class name you defined for the model) as well as its type (the class you defined for the model). Each method also accepts a variety of other parameters, whatever may be necessary to support the unique behavior of that method.
Note that each method of the IAutoEntityService
interface is optional. Implement only what you need!
In addition to the basic CUD operations, we also support bulk versions of each CUD operation, as well as several options for loading entities. Each of the different load operations support different behavioral semantics, defined by the initiating actions and guiding how the meta reducer handles the data in state.
Each method of the IAutoEntityService
interface accepts, as the last parameter, custom criteria
. This allows you, the developer, to include any additional, arbitrary details that may be necessary to facilitate the desired operation, for any operation supported by this library.
In addition to the modelName
, the @Entity
decorator supports other names. These include the pluralName
which can be useful for dynamically describing entities in plurality in log messages, messages displayed in the ui, etc. Additionally a uriName
may be specified that can be used when building API endpoint urls in your entity services.
Some examples of using the @Entity
decorator to name your entities:
Auto-entity provides a number of utility functions for retrieving the various names of entities. Refer to the Utility Functions documentation for more information.
Note that each entity class may only be decorated by the @Entity
decorator once! The above multiple uses is only for example's sake.
Foundations Laid
Entity models are central to Auto-Entity. These describe your data, not just in terms of the data itself, but how Auto-Entity interacts with each entity. The @Entity
decorator allows you to configure each entity, including specifying non-minifiable entity names, comparers for sorting purposes, transforms for converting data to and from server formats, and more. The @Key decorator allow you to mark which properties of your entities comprise the unique identifier of each.
A range of metadata may be used to configure your entities using the @Entity
decorator. This decorator is intended to decorate the classes you use to define your entity models. The only required property is the modelName
, which provides auto-entity with a non-minifiable name that uniquely describes the identifier of your model. A non-minifiable name (i.e. vs. constructor.name
) is critical, as auto-entity relies on this name to find the slice of state for your entity and perform other internal operations.
In addition to the model name, the @Entity
decorator allows you to describe a default sort comparer as well as named sort comparers, compose pipelines of data transforms, define a default maximum age for entities for use with optional loading, and finally exclude the entity from certain automatic effects handling (for very advanced use cases).
For any entity to be managed by auto-entity, it must have an identity. A unique identity. Auto-entity supports entity keys that are single-property, or multi-property. A multi-property or composite key will be handled automatically by auto-entity. There is an internal format for these that ensures they are strings that can be used as keys in the ngrx state.
Entity keys may be retrieved with one of many key getter utility functions. Check the Utility Functions documentation for more information.
Sorting is a core feature of most applications that use data. At one point or another, you will likely want to display your data sorted in one order or another. Auto-Entity provides built in functionality for sorting your entities. The most common use case would be to define a default comparer, however auto-entity also supports defining additional named comparers to sort your entities in any number of ways.
To attach a default comparer to your entity, use the comparer
property of the configuration object you pass to the @Entity
decorator. This is a standard comparer function that you should be familiar with for standard JavaScript sort functionality.
Sort comparers may be defined directly within the @Entity
decorator, or may be defined as a constant or function and passed in the decorator. This allows a single comparer to be used for multiple entities, as appropriate:
If you find yourself in a situation where you need to sort entities in more than one way, then you can define named comparers. Named comparers are used with the the CustomSorted selector, which is a parameterized selector. Being parameterized, each time you use it with a different parameter, its memoized result is recomputed. This ensures that you don't use too much memory when sorting the same set of entities in different ways, however at greater compute cost.
To define named comparers, use the comparers
property of the configuration object you pass to the @Entity
decorator. This property expects an object, or an associative map, where the properties are the names of the comparers, and the value of each property is the comparer function.
When defining named comparers, you may also define the default within the comparers
property of the configuration object passed to @Entity
. This is an alternative method of defining a default comparer.
Note that when defining default comparers, if you define both the comparer
property as well as the default
named comparer of the comparers
property, the comparer defined on the comparer
property will always take precedence. As such, this configuration will sort by name, not id:
One of the most powerful features that Auto-Entity provides is data transforms. The data transformation model in auto-entity allows simple types that expose two functions to be composed together in a simple manner to create rich, powerful transformations of entities as they arrive from the server, or are sent to the server. Transforms can solve a multitude of problems that can often be challenging to resolve in other ways.
A transform in auto-entity is a simple object that contains two properties, fromServer
and toServer
. Each of these properties must return a function that can be used to apply the transform to data going in the specified direction. A simple example:
This simple object and its functions perform the very simple task of doubling a name when an entity is retrieved from a server, then un-doubling it when it is sent back to the server. The functions can be extremely basic, in this case they leverage a JavaScript comma expression to assign a property then return the updated object back without requiring any unnecessary verbosity.
You may notice that the functions in the previous example do not appear to be pure. They are modifying the object passed in! That may seem as though it violates immutability, however it helps to understand the context within which these functions are called. Transformation occurs BETWEEN the entity services, and the effects that handle auto-entity actions. The transformation code will always clone your entity first before applying each configured transformation in-order.
This ensures that whenever an entity is transformed, the process IS immutable. Further, all the apparent non-purity is tightly contained within a specific function of the transformation process, and therefor no mutations are actually observable to anything other than the transformation process itself. Such "mutative contexts" are often allowed in functional languages that otherwise require immutability. If mutations cannot be observed, then they cannot incur unexpected side effects. No transformation mutation can ever be observed outside of the transformation process itself.
The key reason why mutations (of the clone of the entity provided by the internal transformation process) are allowed during transformations is to ensure they are performant. If every entity in a retrieved data set required the entity to be cloned in each and every transform, the performance of the transformation process would be rather miserable (a reality verified through testing!) By allowing mutations in a tightly controlled and non-observable process, transformation can in fact be extremely fast, even with complex transformation pipelines.
Transforms in auto-entity are intended to be composable. This allows discrete transforms of certain aspects of each entity to be encapsulated, often into generalized and reusable units, that can then be composed together as necessary for each entity that requires such transforms.
Some of the most common applications for data transformation is to convert data from a server or wire representation into a more useful representation in the application. A key example of this is dates, which are most often transferred over the wire as ISO8601 formatted strings, as JavaScript Date
objects cannot be properly serialized and deserialized in JSON.
A date transform can make short work of keeping the data types and formats in line both for the app as well as for the wire:
This simple unit of code is a highly reusable transform. Unlike the simpler example before, this transform is actually a function that takes a property name as a parameter, and returns a transform that can transform that property on an entity. This allows the transform to be applied to many entities with different date properties, or even many date properties on a single entity:
Data conversions are likely to be the most common use case for using transforms, however they are not the only use case.
Another potential use case for transforms could be the distribution of information from one particular property, into several others that only exist for UI purposes, then the aggregation of those properties back into a single property for transfer over the wire back to the server.
Sometimes rich information may be encoded in a single string. Such a string may represent numbers and names and other things. These aspects of the entity can be distributed out of the single string, into distinct properties of the entity making them easier to access and strongly typed.
Finally, transforms may be used to generate data. Perhaps from other data on the entity, perhaps from data produced by another transform, which in effect can chain transforms. In the event that transforms are chained together, the order in which they are specified in the transform
property of the configuration passed to @Entity
becomes important.
To get a named comparer for an entity, you can use the comparer . Further, you can use the customSorted
selector and pass the name of the comparer you wish to use for the sort, which will sort with that comparer on selection.
Initialization Simplified
At the core of NgRx Auto-Entity is the buildState
function. This utility function encapsulates the creation and management of all the "boilerplate" that you would normally implement yourself when building up state for a new entity. 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!)
Boilerplate Encapsulated
The buildState
function has a fairly strait forward 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 (including extra custom state), 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.
If you examine the IModelState
interface closely, including all of its generic types, you may notice the TExtra
type. This represents extra, custom state properties you may add to your auto-entity state. This is provided as the second argument passed to buildState
, and allows any additional state to be added.
Auto-entity aims to preserve the full typing of your entity states, and if extra state is provided, the type of your state will become TState & TExtra
, or the intersection of the two types. This should ensure that when your state is used, in selectors for example, the full type information is available.
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 variety 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.
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.
Radical Simplification with Reusable Action Libraries
NgRx Auto-Entity provides a relatively simple solution to the action triplet trifurcation conundrum. Make commonly-implemented actions reusable! If there ever was a use case for generics, CRUD entity actions would seem to be as sublimely perfect a case as ever possible. By making actions generic, this allows a simple library of standard CRUD actions to be implemented that could then be easily reused by any number of unique entities.
Generic actions are the primary interface with which an application interacts with NgRx Auto-Entity. No need to implement the same pattern of three actions again and again for each and every entity! We have provided all the necessary actions for you. You simply need to dispatch them!
Aside from no longer having to implement your own actions, everything else should be business as usual. Your controllers, when dispatching generic actions from Auto-Entity directly, should look quite familiar.
In future versions of NgRx Auto-Entity, legacy-style action classes (i.e. new LoadAll(...)
) will be supplemented by NgRx 8+ style factory functions. Instead of newing up an action, you'll be able to use an action factory pre-typed specifically to your entities:
loadAllCustomers()
createOrder(order)
selectUserByKey(userId)
This stylistic alignment should simplify NgRx Auto-Entity usage and allow deeper integration of auto-entity generated functionality with standard NgRx code, such as reducers created with createReducer()
. (These changes are currently slated for version 0.6)
Each generic action's constructors require a standard set of parameters, and some actions may require unique parameters to perform the necessary function. Most action constructors are as follows: