An EntityCollectionService<T>
is a facade over the ngrx-data dispatcher and selectors$ that manages an entity T
collection cached in the ngrx store.
The Dispatcher features command methods that dispatch entity actions to the ngrx store. These commands either update the entity collection directly or trigger HTTP requests to a server. When the server responds, the ngrx-data library dispatches new actions with the response data and these actions update the entity collection.
The EntityCommands
interface lists all the commands and what they do.
Your application calls these command methods to update the cached entity collection in the ngrx store.
Selectors$ are properties returning selector observables. Each observable watches for a specific change in the cached entity collection and emits the changed value.
The EntitySelectors$
interface
lists all of the pre-defined selector observable properties and
explains which collection properties they observe.
Your application subscribes to selector observables in order to process and display entities in the collection.
Here are simplified excerpts from the demo app's HeroesComponent
showing the component calling command methods and subscribing to selector observables.
constructor(EntityCollectionServiceFactory: EntityCollectionServiceFactory) {
this.heroService = EntityCollectionServiceFactory.create<Hero>('Hero');
this.filteredHeroes$ = this.heroService.filteredEntities$;
this.loading$ = this.heroService.loading$;
}
getHeroes() { this.heroService.getAll(); }
add(hero: Hero) { this.heroService.add(hero); }
deleteHero(hero: Hero) { this.heroService.delete(hero.id); }
update(hero: Hero) { this.heroService.update(hero); }
The component injects the ngrx-data EntityCollectionServiceFactory
and
creates an EntityCollectionService
for Hero
entities.
We'll go inside the factory later in this guide.
Alternatively, you could have created a single HeroEntityService
elsewhere, perhaps in the AppModule
, and injected it into the component's constructor.
There are two basic ways to create the service class.
- Derive from
EntityCollectionServiceBase<T>
- Write a
HeroEntityService
with just the API you need.
When HeroEntityService
derives from EntityCollectionServiceBase<T>
it must inject the EntityCollectionServiceFactory
into its constructor.
There are examples of this approach in the demo app.
When defining an HeroEntityService
with a limited API,
you may also inject EntityCollectionServiceFactory
as a source of the
functionality that you choose to expose.
Let your preferred style and app needs determine which creation technique you choose.
The component sets two of its properties to two of the EntityCollectionService
selector observables: filteredEntities$
and loading$
.
The filteredEntities$
observable produces an array of the currently cached Hero
entities that satisfy the user's filter criteria.
This observable produces a new array of heroes if the user
changes the filter or if some action changes the heroes in the cached collection.
The loading$
observable produces true
while the
data service is waiting for heroes from the server.
It produces false
when the server responds.
The demo app subscribes to loading$
so that it can turn a visual loading indicator on and off.
Note that these component and EntityCollectionService
selector property names end in '$'
, a common convention for a property that returns an Observable
.
All selector observable properties of an EntityCollectionService
follow this convention.
For brevity, we'll refer to them going forward as selector$
properties or selectors$
.
Note that these
selector$
properties (with an's'
) differ from the closely-relatedselector
properties (no'$'
suffix), discussed elsewhere.A
selector
property returns a function that selects from the entity collection. That function is an ingredient in the production of values for its correspondingselector$
property.
The component class does not subscribe these selector$
properties but the component template does.
The template binds to them and forwards their observables to the Angular AsyncPipe
, which subscribes to them.
Here's an excerpt of the filteredHeroes$
binding.
<div *ngIf="filteredHeroes$ | async as heroes">
...
</div>
Most of the HeroesComponent
methods delegate to EntityCollectionService
command methods such as getAll()
and add()
.
There are two kinds of commands:
- Commands that trigger requests to the server.
- Cache-only commands that update the cached entity collection.
The server commands are simple verbs like "add" and "getAll".
They dispatch actions that trigger asynchronous requests to a remote server.
The cache-only command methods are longer verbs like "addManyToCache" and "removeOneFromCache" and their names all contain the word "cache". They update the cached collection immediately (synchronously).
Most applications call the server commands because they want to query and save entity data.
Apps rarely call the cache-only commands because direct updates to the entity collection are lost when the application shuts down.
Many EntityCollectionService
command methods take a value.
The value is typed (often as Hero
) so you won't make a mistake by passing in the wrong kind of value.
Internally, an entity service method creates an entity action that corresponds to the method's intent. The action's payload is either the value passed to the method or an appropriate derivative of that value.
Immutability is a core principle of the redux pattern.
Several of the command methods take an entity argument such as a Hero
.
An entity argument must never be a cached entity object.
It can be a copy of a cached entity object and it often is.
The demo application always calls these command methods with copies of the entity data.
The current ngrx libraries do not guard against mutation of the objects (or arrays of objects) in the store. A future ngrx freeze feature will provide such a guard in development builds.
All command methods return void
.
A core principle of the redux pattern is that commands never return a value. They just do things that have side-effects.
Rather than expect a result from the command, you subscribe to a selector$ property that reflects the effects of the command. If the command did something you care about, a selector$ property should be able to tell you about it.
The create<T>()
method of the ngrx-data EntityCollectionServiceFactory
produces a new instance of the EntityCollectionServiceBase<T>
class that implements the EntityCollectionService
interface for the entity type T
.