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.
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.
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.
To get a named comparer for an entity, you can use the comparer Utility Functions. 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.
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: