The ngrx-data library expects to persist entity data with calls to a REST-like web api with endpoints for each entity type.
The EntityDataService
maintains a registry of service classes dedicated to persisting data for a specific entity type.
When the ngrx-data library sees an action for an entity persistence operation, it asks the EntityDataService
for the registered data service that makes HTTP calls for that entity type, and calls the appropriate service method.
A data service is an instance of a class that implements the
EntityCollectionDataService<T>
interface.
This interface supports a basic set of CRUD operations that return Observables
:
Method | Meaning |
---|---|
add(entity: T) |
Add a new entity |
delete(id: any) |
Delete an entity by primary key value |
getAll() |
Get all instances of this entity type |
getById(id: any) |
Get an entity by its primary key |
getWithQuery(queryParams: QueryParams | string) |
Get entities that satisfy the query |
update(update: Update<T>) |
Update an existing entity |
QueryParams
is a parameter-name/value map You can also supply the query string itself.HttpClient
safely encodes both into an encoded query string.
Update<T>
is an object with a strict subset of the entity properties. It must include the properties that participate in the primary key (e.g.,id
). The update property values are the properties-to-update; unmentioned properties should retain their current values.
The default data service methods return the Observables
returned by the corresponding Angular HttpClient
methods.
If you create your own data service alternatives, they should return similar
Observables
.
The EntityDataService
registry is empty by default.
You can add custom data services to it by creating instances of those classes and registering them with EntityDataService
in one of two ways.
-
register a single data service by entity name with the
registerService()
method. -
register several data services at the same time with by calling
registerServices
with an entity-name/service map.
You can create and import a module that registers your custom data services as show in the EntityDataService tests
If you decide to register an entity data service, be sure to do so before you ask ngrx-data to perform a persistence operation for that entity.
Otherwise, the ngrx-data library will create and register an instance of the default data service DefaultDataService<T>
for that entity type.
The demo app doesn't register any entity data services. It relies entirely on a DefaultDataService<T>
, created for the entity type by the injected DefaultDataServiceFactory
.
A DefaultDataService<T>
makes REST-like calls to the server's web api with Angular's HttpClient
.
It composes HTTP URLs from a root path (see "Configuration" below) and the entity name.
For example,
- if the persistence action is to delete a hero with id=42 and
- the root path is
'api'
and - the entity name is
'Hero'
, then - the DELETE request URL will be
'api/hero/42'
.
When the persistence operation concerns multiple entities, the DefaultDataService
substitutes the plural of the entity type name for the resource name.
The QUERY_ALL
action to get all heroes would result in an HTTP GET request to the URL 'api/heroes'
.
The DefaultDataService
doesn't know how to pluralize the entity type name.
It doesn't even know how to create the base resource names.
It relies on an injected
HttpUrlGenerator
service those.
And the default implementation of that generator relies on the
Pluralizer
service to
get the collection resource name.
The Entity Metadata guide
explains how to configure the default Pluralizer
.
The collection-level data services construct their own URLs for HTTP calls. They typically rely on shared configuration information such as the root of every resource URL.
The shared configuration values are almost always specific to the application and may vary according the runtime environment.
The ngrx-data library defines a DefaultDataServiceConfig
class for conveying shared configuration to an entity collection data service.
The most important configuration property, root
, returns the root of every web api URL, the parts that come before the entity resource name.
For a DefaultDataService<T>
, the default value is 'api'
, which results in URLs such as api/heroes
.
The timeout
property sets the maximum time (in ms) before the ng-lib persistence operation abandons hope of receiving a server reply and cancels the operation. The default value is 0
, which means that requests do not timeout.
The delete404OK
flag tells the data service what to do if the server responds to a DELETE request with a 404 - Not Found
.
In general, not finding the resource to delete is harmless and
you can save yourself the headache of ignoring a DELETE 404 error
by setting this flag to true
, which is the default for the DefaultDataService<T>
.
When running a demo app locally, the server may respond more quickly than it will in production. You can simulate real-world by setting the getDelay
and saveDelay
properties.
While the ngrx-data library provides a configuration object to modify certain aspects of the DefaultDataService, you may want to further customize what happens when you save or retrieve data for a particular collection.
For example, you may need to modify fetched entities to convert strings to dates, or to add additional properties to an entity.
You could do this by creating a custom data service and registering that service with the EntityDataService
.
To illustrate this, the sample app adds a dateLoaded
property to the Hero
entity to record when a hero is loaded from the server into the ngrx-store entity cache.
This could be useful if you replace stale heroes periodically.
export class Hero {
readonly id: number;
readonly name: string;
readonly saying: string;
readonly dateLoaded: Date;
}
To support this feature, we 'll create a HeroDataService
class that implements the EntityCollectionDataService<T>
interface.
In the sample app the HeroDataService
derives from the ngrx-data DefaultDataService<T>
in order to leverage its base functionality.
It only overrides what it really needs.
// store/entity/hero-data-service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { EntityCollectionDataService, DefaultDataService, HttpUrlGenerator, Logger, QueryParams } from 'ngrx-data';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Hero } from '../../core';
@Injectable()
export class HeroDataService extends DefaultDataService<Hero> {
constructor(http: HttpClient, httpUrlGenerator: HttpUrlGenerator, logger: Logger) {
super('Hero', http, httpUrlGenerator);
logger.log('Created custom Hero EntityDataService');
}
getAll(): Observable<Hero[]> {
return super.getAll().pipe(map(heroes => heroes.map(hero => this.mapHero(hero))));
}
getById(id: string | number): Observable<Hero> {
return super.getById(id).pipe(map(hero => this.mapHero(hero)));
}
getWithQuery(params: string | QueryParams): Observable<Hero[]> {
return super.getWithQuery(params).pipe(map(heroes => heroes.map(hero => this.mapHero(hero))));
}
private mapHero(hero: Hero): Hero {
return { ...hero, dateLoaded: new Date() };
}
}
This HeroDataService
hooks into the get operations to set the Hero.dateLoaded
on fetched hero entities.
It also tells the logger when it is created (see the console output of the running sample) .
Alternatively, you might write your own complete implementation of
DefaultDataService<T>
. It depends on the needs of your application.
Finally, we must tell ngrx-data about this new data service.
The sample app provides HeroDataService
and registers it by calling the registerService()
method on the EntityDataService
in the app's entity store module:
// /store/entity-store.module.ts (excerpt)
...
import {
...
EntityDataService, // <-- import the ngrx-data data service registry
} from 'ngrx-data';
...
import { HeroDataService } from './hero-data-service';
@NgModule({
imports: [ ... ],
providers: [
...
HeroDataService, // <-- provide the custom data service
]
})
export class EntityStoreModule {
constructor(
entityDataService: EntityDataService,
heroDataService: HeroDataService,
...
) {
// Register custom EntityDataServices
entityDataService.registerService('Hero', heroDataService); // <-- register it
}
}