For those concerned about performance, there is a performance hit to making POJOs into properly prototyped objects. It is not a free process, however NgRx Auto-Entity utilizes the fastest known mechanism at the moment in order to create an object with the right prototype and properties.
The current approach used to create an object with the proper prototype and the same properties and data as the reference object utilizes the Object.create and Object.assign functions from standard ECMAScript. There are other ways to attach a prototype, however they are not ideal due to performance concerns.
The current implementation of makeEntity
is as follows:
This creates a new object that uses the same prototype as your entity model class. Once that object is created, the properties of your supplied reference object are assigned to the newly created and properly prototyped object.
Under basic testing, `makeEntity` will convert 100,000 POJOs into prototyped objects in about 60-70 milliseconds. It's performance impact is not particularly significant, however it does add overhead. As such, currently in v0.5.x, we are not converting entities implicitly internal to Auto-Entity. We continue to leverage the model type for metadata. We may provide a configurable option in a future version of Auto-Entity to force conversion of all entities that are touched by any auto-entity library code, to ensure that entities that are retrieved from auto-entity state are always properly prototyped.
You may wonder why Object.assign
rather than an object spread {...obj}
. Wouldn't a spread do the same thing? Not quite. While in many circumstances, the result of performing an object spread and Object.assign are similar, there is a nuanced difference: Object.assign sets properties, while the spread operator defines properties.
The spread operator is inherently a creation operator, creating everything as it works through the root level of the object being spread. The assign function on the other hand will set each property on the target object. If the target has existing properties defined for a given property name, then the setters of those properties will be called during the assignment. If you have created rich model types with custom property definitions, then Object.assign is the only way to properly convert a simple POJO into a proper entity that conforms to all of the rules of your model type.
It should also be noted that makeEntity
is a shallow operation. Since the @Entity
decorator only applies to the root model, there is currently no need to perform a deep clone. As such, the performance impact of making an entity from a POJO is only one level deep.
You may wonder why we do not use the setObjectPrototype
function on Object
given that it is a standard function and has been supported by browsers for some time now. Sadly, this function has known performance limitations, limitations that are not restricted to just the execution of the initial call.
According to the MDN documentation on setObjectPrototype
, the effects of reassigning the prototype of an object may persist on that object instance for its entire lifetime, and affect every piece of code that has access to the object. While simply reassigning the prototype of an existing object would seem more ideal than creating a new object with the proper prototype, until such time as this function is implemented in a highly efficient manner...and more specifically a manner that does not impose secondary performance concerns...we will not be using it within NgRx Auto-Entity.
NgRx Auto-Entity ships with numerous utility functions to help you work with your auto entities. This includes utilities to retrieve the various entity names (model, plural, and uri), custom comparers, transforms, and more.
Some utility functions require you to pass in the model type. This is the class
of the model that defines the entity, rather than instances of that class. This is due to the fact that entity metadata is attached to the model class via the @Entity
and @Key
decorators. Unless you explicitly convert your object instances to your model types using another utility function, generic plain old javascript objects will not directly include the necessary metadata required to determine much of the information these utility functions provide.
Maker of Entities!!
If you have read through the previous documentation, or used NgRx Auto-Entity for some time, you may have noticed the prevalence of the model type in method and function signatures throughout the library. This requirement on the model type (the class you define your entities with, the class you decorate with `@Entity`) is to ensure that the necessary metadata required for auto-entity to do its automatic magic is available.
Due to the lack of automatic typing, NgRx Auto-Entity requires, in most of its functions, methods and constructors, the model type itself (the class, not objects created from the class) to be provided. This ensures that Auto-Entity has access to the necessary metadata. Any time you need to retrieve the metadata for an entity, you can always find a function that accepts the model type, and possibly an entity instance, to retrieve that metadata, or some aspect of it (i.e. such as the entity key).
When creating an entity instance you will rarely be creating them from the model type itself. Instead, you will most likely create simple, plain old javascript objects, or POJOs for short. Something like this:
Such an object is prototyped only by Object
itself, not your custom entity model type. If you were to try and use this model with any function or method in Auto-Entity that requires entity metadata (provided by the @Entity
and @Key
decorators), you would sadly discover that no such metadata exists. Not on your POJO object instance, anyway.
Not to fret, we have a solution to this small little problem. If you wish to make sure your entities are properly prototyped by your model types, you can call the makeEntity
function. This function simply converts a POJO into a properly prototyped entity.
The `makeEntity` function is a curried function. This means, generally speaking, that it is a function who's arguments have been broken into subsequent function calls. It is also a higher order function, in that the outer function returns another function.
The outer function requires the model type as a parameter. It then returns another function that can make entities of that type. The makeEntity
function is intended to be used in a particular manner, like so:
This approach allows you to create a maker function for specific entity types, and reuse those functions over and over within your application.
NgRx Auto-Entity actually makes it easier for you. Whenever you call buildState()
, an entity maker function is created for you. You simply need to destructure it from the standard result:
Who goes there?
Every automatic entity can be decorated with up to three distinct names. The modelName
which is the standard, unique name of the entity, even in minified, uglified, optimized code. The pluralName
which is the name of the entity that may be used in messaging to the end user. The uriName
which is the name of the entity as it should be used in urls to RESTful APIs.
All of these functions may be passed a model type, or an entity instance, so long as the instance is properly prototyped by the model type. Utilize the previously described makeEntity
function to convert POJOs into prototyped entities.
You may retrieve any of these names using the entity name utility functions. The standard name of any entity, the name used in state, and the core name used by auto-entity to identify your model, state, etc. may be retrieved with the nameOfEntity()
function.
Auto-Entity allows you to decorate your entities with a plural name as well. Internally, auto-entity does not use this directly. It is intended to allow easy retrieval of a properly pluralized name in the end developer's code. Notably, this may be the case in a dynamic entity service created by the end developer, when logging messages about the entity, etc. You may retrieve this name with the pluralNameOfEntity()
function.
A common use case for pluralName
is when generating dynamic messages about entity actions. For example, an effect that toasts an error whenever a Failure action is dispatched might use the plural name for any of the "All", "Many", "Page" or "Range" actions:
Another name Auto-Entity supports is the URI name. This name is explicitly provided in the event that the name of your entity, as used in urls to API endpoints. Even if an entity name in a url is plural, they are often formatted differently...such as kebab case. You may retrieve this name with the uriNameOfEntity()
function.
A common use case is when implementing auto entity services. The uriName
allows urls to be dynamically built from the entity metadata, allowing the developer to share entity services across entities:
Note that it is also possible to use just info.uriName
as well, as the entity options are supplied along with the modelType
on IEntityInfo
. The use of uriNameOfEntity()
is an alternative method of getting the name.
Another use case is when implementing a dynamic entity service that supports hierarchical API endpoints. An example may be getting LineItem
s for Order
s. You may need to build a structured url along the lines of /orders/:order-id/line-items/:line-item-id
in order to make the proper request. The use of custom criteria can assist here:
Custom criteria may be passed along with entity actions, say from an effect:
The final entity name supported by Auto-Entity is the state name. The state name is the name that Auto-Entity expects to be used when you include your entity state within either app state or feature state, and is derived from the modelName
. Auto-entity uses this name internally when accessing state and when messaging about the state property. You may retrieve this name with the stateNameOfEntity()
function.
Identity please!
Along with entity name utility function, NgRx Auto-Entity also provides a number of key retrieval functions. Entity keys are a first class concept in Auto-Entity, with support for string or numeric keys as well as composite keys. In many cases you may simply know the keys of your entities and may be able to use them directly. In other cases, you may be using composite keys or may be working with many dynamic entities that do not have known specifics. The entity key utility functions are available for these cases.
The original key retrieval utility, used in the library from its early days, is getKey()
and is used internally for most key retrieval. This method allows you to provide an IEntityAction
along with an entity instance, and retrieve the key of the entity.
Since a lot of the internal functionality of Auto-Entity has access to the actions being dispatched, this is the primary method of key retrieval by the framework itself. It may not be as useful for the end developer.
An additional method added in v0.2, allows retrieval of an entity's key by providing the model class and an entity instance. This is the primary use case for the end developer in most cases, as the model class should be readily available.
If you need to get a key from an entity, this is the recommended approach.
A final method, added in v0.5, allows the retrieval of an entity's key by providing a properly typed (properly prototyped) entity instance (i.e. via makeEntity
). This method is a use case if you intend to convert your entities to properly typed objects as a matter of course.
Conversion of plain objects to entities can be useful for certain designs and usage goals, where having richer entity models and actually using them is important. This new function aims to support such richer models.
Auto-Entity provides some rich sorting capabilities. Every entity may define a default comparer as well as one or more named comparers. Auto-entity uses these utility functions internally whenever you use one of the sort selectors, however one of these functions is also exposed in the public API in the event you need to perform your own sorts of your own entities. This may occur often enough in custom selectors, where entities sorted in specific orders may be necessary for selectors to function properly.
The utility function for getting comparers for an entity is entityComparer()
which encapsulates all of the comparer retrieval functionality. Using this function, you may retrieve the default comparer or any named comparer, for an entity specified by its model type or a prototyped entity object. You may then use that comparer with Array.sort
.
Normally, you will want to pass the entity model along with the name of the comparer. If you wish to retrieve the default comparer, the name is optional.