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