NgRx Auto-Entity offers many ways to load data. The most common of these will usually be the single, all and many loads. A single entity load is pretty strait forward, requiring that you provide a model type and entity key to the Load
action. This will retrieve the single entity uniquely identified by the specified key and merge it into the state for that entity.
There are several terms I will use throughout this documentation when referring to what kind of changes are performed on state as a result of an action.
Merged: State that is merged will add or update existing entities in state with entities of the action being reduced
Replaced: State that is replaced will drop any existing state in favor of the entities of the action being reduced
Concatenated: State where the entities of the action are simply added to whatever is already in state
Loading all entities of a given type is even more strait forward, as you simply provide the model type to the LoadAll
action. This will retrieve every entity of a given type and replace it in the state for that entity.
The third common way of loading entities is to load many entities. This is also strait forward and relies on providing at least a model type to the LoadMany
action. This will retrieve entities and merge them into the state for that entity.
Note that when the behaviors of actions are described here, this is the expected behavior for a given action. Since implementation of Entity services is still the responsibility of the developer, it is up to the developer top ensure they conform to the expectations of each they are handling in their Entity services.
For many loads, you may need to specify criteria other than a primary or uniquely identifying key. All NgRx Auto-Entity actions support optional custom criteria that may be used for this purpose.
In addition to our "standard" loading actions for single, all and many entity retrievals, we also provide two additional loading actions: LoadPage
and LoadRange
. You may wonder why both, and again it boils down to semantics, behavior. Both are necessary to support different kinds of data loading behavior, as will become evident here.
Loading a "range" of data is similar in concept to loading a page of data, however it differs in semantics and actual behavior in the context of NgRx Auto-Entity and the way reduction of page loads are handled vs. the way reduction of ranged loads are handled.
The LoadRange
generic action supports a more modern approach to handling very large, and more specifically arbitrarily sized datasets called "infinite scrolling." This technique is usually implemented on lists of items, such as products in a catalog, that may have relatively arbitrary and often-changing total entity counts. When a user scrolls to the bottom of the browser page, the next "range" of products, starting from the last+1 of the currently displayed range, is automatically retrieved from the server and displayed as a continuation of any previously displayed list.
When loading a range, unlike loading a page, any newly loaded entities for this type will be concatenated into existing state, preserving any previously loaded entities and including any newly loaded entities. As with paged loads, this ensures that bandwidth is used efficiently, however keep in mind that for very large data sets, continuous loading of subsequent ranges could eventually require significant amounts of browser memory to store all the information in state. Further, due to the pure
(side-effect free) nature by which new versions of state must be created with @ngrx, updating state with a large volume of previously loaded entities may start to become a lengthy operation.
Loading ranges of data one at a time with NgRx Auto-Entity is also very easy. Simply use the LoadRange
action, or the loadRange
method on your entity facades, along with the Range
parameter:
When implementing your entity service, you will have access to the Range
object provided in the initial action, which may be leveraged in your API call:
Note the use of pipe()
on the http
call here, and the transformation of the response. Also not the return type of the loadRange
method. NgRx Auto-Entity requires additional meta-data about what range was loaded as well as the total number of entities that may be ranged through, in order to store that information in state and make it available to components and your UI (for proper handling of infinite scrolling behavior, for example).
It is important to understand the nature of the data that must be returned by the loadRange
method in your entity services. The return type is IEntityWithRangeInfo<TModel>
which is defined as so:
The child type of the property rangeInfo
, of type IRangeInfo
is defined as so:
Unlike with paged data, where a total count is essential in order to properly calculate how many pages of data there are, ranged data does not necessarily require an exact total count. For use cases where a total count is not required, you may simply specify Infinity
or any other constant that allows you to implement your ranged loads and UI behavior properly.
In the event that you do require an actual total count, see the paged loads documentation for various ways to retrieve total counts from the server.
One,All, Many, Pages, Ranges...
The generic actions in the NgRx Auto-Entity library cover all the CRUD bases, as well as a range of loading options, plus some extra useful "utility" actions to cover other common state-related entity tasks. First up, we provide several options for loading data:
Load: one entity at a time
LoadAll: every entity all at once
LoadMany: lots of entities in arbitrary bunches
LoadPage: entities in discrete bunches
LoadRange: entities in sequential bunches
Each load action is imbued with unique semantics and behavioral implications. For example, dispatching a LoadAll
action implies that you wish to replace any previously existing entity state for the given model with whatever set of entities is retrieved by the entity service for that model.
Dispatching LoadMany
on the other hand implies that you wish to keep any previously existing entity state for the given model, and merge in whatever set of entities is retrieved. Similarly, Load
will also merge the retrieved entity into any previously existing state.
Further, pages are semantically different than ranges. A page is a discrete slice of a set of entities with a distinct beginning and end. A range on the other hand is a sequential slice of a set of entities that ultimately form a continuous range. When dispatching LoadPage
the implication is that you wish to replace any previously existing state for the given model.
The explicit use case here is when the entire set of all entities for a given model is simply too large to fit in memory in a browser (i.e. you may have tens of thousands...millions...billions of records in a database.) Standard case for tables with paging controls.
When dispatching LoadRange
on the other hand, the implication is that you wish to join newly loaded ranges of entities onto existing state for the given model. The primary use case here is infinite scrolling, where additional entities are added to previously loaded entities in response to continued vertical scrolling or horizontal panning by a user.
In addition to our "standard" loading actions for single, all and many entity retrievals, we also provide two additional loading actions: LoadPage
and LoadRange
. You may wonder why both, and again it boils down to semantics, behavior. Both are necessary to support different kinds of data loading behavior, as will become evident here.
Loading of paged data is a common practice that dates back many years, in fact decades. It is a fundamental kind of data load, and extensive research has been done as well as dedicated features to support paged data have been integrated into data storage solutions.
Loading pages of data, rather than loading complete sets of data, is often essential. This is especially true in the modern world where we frequently must acquire and work with immense datasets that can number in the millions, billions...even trillions of records. With such numbers, it should be obvious that loading all entities for such datasets would be problematic at best, assuming it was even possible in the first place. Large datasets will usually consume immense space, including immense bandwidth and immense amounts of memory in a browser.
When loading a page, any previously loaded entities for this type will be replaced in by the new page of entities retrieved. This ensures that bandwidth and browser memory space are used efficiently, and that memory is not wasted trying to store an ever-growing number of entities as a user cycles through pages in your UI.
Loading pages of data one at a time with NgRx Auto-Entity is quite easy. Simply use the LoadPage
action, or the loadPage
method on your entity facades, along with the Page
parameter:
When implementing your entity service, you will have access to the Page
object provided in the initial action, which may be leveraged in your API call:
Note the use of pipe()
on the http
call here, and the transformation of the response. Also note the return type of the loadPage
method. NgRx Auto-Entity requires additional meta-data about which page was loaded as well as the total number of entities that may be paged through, in order to store that information in state and make it available to components and your UI (for proper rendering of paging controls, for example).
It is important to understand the nature of the data that must be returned by the loadPage
method in your entity services. The return type is IEntityWithPageInfo<TModel>
which is defined as so:
The child type of the property pageInfo
, of type IPageInfo
is defined as so:
One way or another, you must be able to provide the above information for each paged load. You may simply prefer to reflect back the original page information sent in for the IPageInfo
page property, however usually the totalCount
must be returned by the server in one way or another. It need not necessarily be on the same call, an additional call may be performed to retrieve the total count of entities:
Another possibility, although potentially not canonical standard depending on exactly how you implement it, might be to use the HTTP Content-Range
header in your responses to support additional metadata about page info, without having to include it in the body of the response. This header is normally used with a unit of bytes, however technically speaking the units can be arbitrary (such as, say, records
or entities
):