I am trying to apply the @ngrx/data module into my application. But there are a couple of things I am trying to figure out the best way (or right way) to implement custom behavior for my applications. So I want to raise this issue to seek help from the community and NGRX team. I can spend time learning and study but I am not sure whether it complies with NGRX team principal and design best practices. I believe that a lot of people interested in using NGRX modules will also interested in and want to help a well-documented guideline.
Handling pagination and store query params to the entity cache. Also, we need a way to cancel or skip a certain operation if data is loaded. For example, I want to avoid load entity collections again if it is cached on the entity cache. Maybe we could add a new prop to the EntityActionOptions (i.e. skipIfLoaded).
From my research, I think we need to implement a custom DefaultPersistenceResultHandler (3 steps from this document https://ngrx.io/guide/data/entity-metadata). And then, I have to write more code to custom data service as well as reducer/effect.
Step by step how to create a new custom action type or entity operation. Almost applications will require custom actions (not only CRUD), so is there any place that I can read and implement it? Should this be a feature for @ngrx/data module? In my research, I knew that I have to create a custom action and dispatch it, but I am not sure what are the remaining steps I need to implement to ensure
I believe that a lot of people interested in using NGRX modules will also interested in and want to help a well-documented guideline.
The issue with the docs is not necessarily that things are missing but that it’s too abstract with links back and forth so you end up missing the “point”. I’m planning on getting more involved in the docs so I’m interested specific pain points with a preference for smaller changes (no one likes it when the supermarket changes where everything is located). That being said I think there needs to be a first class section (mostly a refactor of existing docs) Handling APIs on dealing with the following
Handling APIs
*the full topic of pagination in general is likely too big for this section. The answer will end up being, “it depends” and likely belongs in the FAQ section.
@hoang-innomizetech I love to hear your feedback on this in particular.
If this would be helpful, if sections are appropriate, if there is anything missing...
Pagination and ability to skip action if entity collection is loaded.
Handling pagination and store query params to the entity cache. Also, we need a way to cancel or skip a certain operation if data is loaded. For example, I want to avoid load entity collections again if it is cached on the entity cache. Maybe we could add a new prop to the EntityActionOptions (i.e. skipIfLoaded).
Edit - sorry, I misread above. Following doesn’t address point you were making.
—-
Ability to skip means checking the loaded status. The “dumb” way is the following:
/**
* service is collection service accessible via EntityServices
*
* const service = this.entityServices.getEntityCollectionService(
* entityName
* );
*/
service.loaded$.pipe(
tap((loaded: boolean) => {
if (!loaded) {
service.getAll();
}
}),
filter(loaded => loaded),
take(1)
).subscribe(...)
Need to think more carefully about error handling though. I’m investigating this.
If I find a good solution I may reopen #2415
Also need to add how to keep this traceable in devtools so you know where getAll was called.
From my research, I think we need to implement a custom DefaultPersistenceResultHandler (3 steps from this document https://ngrx.io/guide/data/entity-metadata). And then, I have to write more code to custom data service as well as reducer/effect.
If all you want is to save the total number of entities that exist and add the entities at same time when a pagination result returns then 3 step DefaultPersistenceResultHandler will work, no need to necessarily use a custom dataservice. Keep in mind #2428
I missed this myself and was pipe mapping inside a dataservice and dispatching a custom action to save the total elsewhere in the state. I envisage my proposed Handling APIs > Pagination would make this clearer.
Stey by step how to create a custom action
Step by step how to create a new custom action type or entity operation. Almost applications will require custom actions (not only CRUD), so is there any place that I can read and implement it? Should this be a feature for @ngrx/data module? In my research, I knew that I have to create a custom action and dispatch it, but I am not sure what are the remaining steps I need to implement to ensure
This is likely depends on your use case.
A few cases could be covered already by https://ngrx.io/guide/data/save-entities#save-with-entitycachedispatchersaveentities
For the general case l’m also so stuck on this.
As far as I can tell extending entity ops, resulting reducer & effects from ngrx data is hard.
My own use case is an exploration of offline patterns. For each potentially failing action like SAVE_ADD_ONE having SAVE_ADD_ONE_OFFLINE_SUCCESS, SAVE_ADD_ONE_OFFLINE_FAIL entity ops, and have a triage in entity effects that decides if we are in online or offline mode and tries to handle actions accordingly.
@AdditionAddict Thank you for the quick reply.
*the full topic of pagination in general is likely too big for this section. The answer will end up being, “it depends” and likely belongs in the FAQ section.
I think we should add more items into the Advanced section so that readers can read more instructions for a certain point. I give you more idea about which items we need, I think I should give you some cases that we need to custom the ngrx/data module:
I also read this page and noticed that it contains some items that I am looking for, but that page isn't completed yet.
```import { Injectable } from '@angular/core';
import {
EntityCollectionServiceBase,
EntityCollectionServiceElementsFactory,
EntityOp,
} from '@ngrx/data';
import { OrderDTO } from './order.dto';
@Injectable({ providedIn: 'root' })
export class OrderDataService extends EntityCollectionServiceBase
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('Order', serviceElementsFactory);
}
changeStatus(data: { newStatus: string; reason: string }) {
const action = {
type: '@ngrx/data/order/change-status',
entityName: 'Order',
// Any way to can custom EntityOps?
entityOp: EntityOp.UPDATE_ONE,
payload: data,
};
this.dispatch(action);
// What else we need to do
// Effects ==> This action should call a certain API endpoint i.e. POST /api/orders/status-change
// Reducers ==> update store
}
anotherAction() {
const action = {
type: '@ngrx/data/order/action',
entityName: 'Order',
// Any way to can custom EntityOps?
entityOp: EntityOp.UPDATE_ONE,
payload: {},
};
this.dispatch(action);
// What else we need to do
// Effects ==> This action should call a certain API endpoint i.e. POST /api/orders/action
// Reducers ==> update store
}
}
```
@AdditionAddict I also noticed a few issues as below:
I am not sure what is wrong with my setup or this is issues from this library. So could you please take a look and let me know. Thank you
On the second issue as the above, dispatch an undo action will revert entity to the original state, but why we don't just dispatch it by default? Our team implemented our own CRUD store library and they did it. So I am curious why we didn't do it.
@AdditionAddict Regarding my concern about adding custom actions (or Entity Op) for a given entity name. Do you have any guidelines or ideas? Right now I am stuck on the EntityOp, it isn't extendable. If I used hard handcrafted action, how do I can implement reducer that can use existing data entity service as well as implement the effect.
@AdditionAddict I also noticed a few issues as below:
The loaded prop always set to false even after the collection is loaded
query many success actions leave the loaded state as it is.
query all success sets loaded to true
optimistic seems not working correctly for update operation, when updating entity with this flag is enabled (isOptimistic: true), our API returns an error but the state didn't reset to the original state
Can you clarify this? What is your error and what version of ngrx data are you using?
On the optimistic issue, I checked the document again and noticed that we have to implement undo action. I just concern why we don't just dispatch undo action by default
@AdditionAddict I am having the same issue as described by @hoang-innomizetech where when I use getByKey, the loaded$ in my store for that given entityCache stays false when it was false.
If this is by design, could you point me to any article or documentation explaining the correct use of it? I've browsed ngrx.io looking for answers but I haven't found any. maybe I haven't looked closely enough.
I am using the loaded$ flag to show/hide my component content and in the router resolver to load the data before navigating into the route that requires the data by id.
If by design the loaded$ flag stays false after a getByKey I wonder what the best implementation for this usual scenario would be.
thanks in advance :)
Hello I really like the librery makes so much faster but you cant provide us with documentation on how to add with it custom actions, effects, how to add it to the reducer.. and how to override existing reducers, actions, effects.
I think all that is possible but for me someone who just started using it it's to complex I feel lost.
If someone has a few minutes MuhamedKarajic is my skype, a bit help would be great!
@AdditionAddict I am having the same issue as described by @hoang-innomizetech where when I use getByKey, the loaded$ in my store for that given entityCache stays false when it was false.
If this is by design, could you point me to any article or documentation explaining the correct use of it? I've browsed ngrx.io looking for answers but I haven't found any. maybe I haven't looked closely enough.
it turns out I found where the explanation of this part is.
not hidden at all but in plain sight.
here is the link in case it is useful for anyone - or myself- in the future
https://ngrx.io/guide/data/entity-collection
and I totally agree @muhamedkarajic , the documentation is frustrating to follow when trying to understand how to extend and override.
I am stuck again trying to make a custom selector for an additional collection state.
I am thinking of putting together a github repository with my solution to maybe use as reference when I am stuck again in the future or for lost people like myself right now struggling through the documentation.
If anyone already knows of any github repo where we can see an implemented solution with all the extension points, it would be of enormous help.
@AdditionAddict I am having the same issue as described by @hoang-innomizetech where when I use getByKey, the loaded$ in my store for that given entityCache stays false when it was false.
If this is by design, could you point me to any article or documentation explaining the correct use of it? I've browsed ngrx.io looking for answers but I haven't found any. maybe I haven't looked closely enough.it turns out I found where the explanation of this part is.
not hidden at all but in plain sight.
here is the link in case it is useful for anyone - or myself- in the future
https://ngrx.io/guide/data/entity-collectionand I totally agree @muhamedkarajic , the documentation is frustrating to follow when trying to understand how to extend and override.
I am stuck again trying to make a custom selector for an additional collection state.
I am thinking of putting together a github repository with my solution to maybe use as reference when I am stuck again in the future or for lost people like myself right now struggling through the documentation.
If anyone already knows of any github repo where we can see an implemented solution with all the extension points, it would be of enormous help.
Please put it I really like the library but it's useless if it's not possible to do this kind of thing. To much heap for nothing, standard requests are not good enough, I have a small app and already 3-4 problems with only 3 services.
I'm in the same boat.
Will follow this thread for future implementation by someone.
It has been now 29 days.no reply from support. i dont have any other option then to do the states as they intended me to do it.
There is a librery Akita I think for anyone who is in this boat this one is better then this ngrx. I'm not here to promote some librery but good competition is here needed.
@muhamedkarajic if you feel that's better for you, feel free to use it. It's not acceptable however to call someone else's hard work crap. Be civil, or get blocked temporarily.
https://www.youtube.com/watch?v=Lu6d_Wt6Cn8
you guys I have recently found this video explaining in great detail plenty of stuff I didn't know about the ngrx/data customization, it has been very useful and I've introduced plenty of his examples into my own work, so I can testify it all works.
Plus all the slides shown in the video are documented in here https://slides.com/jiali/deck-5#/48 where you can find working examples of everything he says during the video.
I would have been happy if I didn't need to had this painful experience but implementing something half done and then not replying to this port at all is for me not acceptable.
I'm grateful for the time they spent but this is not used fully for business. NGRX itself is nice but in Angular we use OOP, not functional principles. The whole way they have done it feels strange for someone coming from an OOP background.
@muhamedkarajic if you feel that's better for you, feel free to use it. It's not acceptable however to call someone else's hard work crap. Be civil, or get blocked temporarily.
I have removed it. Your right but my comment has it's point.
https://www.youtube.com/watch?v=Lu6d_Wt6Cn8
you guys I have recently found this video explaining in great detail plenty of stuff I didn't know about the ngrx/data customization, it has been very useful and I've introduced plenty of his examples into my own work, so I can testify it all works.
Plus all the slides shown in the video are documented in here https://slides.com/jiali/deck-5#/48 where you can find working examples of everything he says during the video.
This dosent solve the issues we have with NGRX DATA only a few samples on how to make things. I know how to use RXJS to combine observables. We need a way to structure the action->effect->reducer part how we want to...
If you find that solution please post it.
@naticaceres I saw from your replies about a month ago, you had similar questions as I do (esp. "I am stuck again trying to make a custom selector for an additional collection state.") and wanted to ask if you maybe have found a solution and would be willing to share that with us?
@hreimer You can try to use the way described here.
Imagine you've added additional fields field1: number, field2: string to Owner entity.
Then you can select field1 like:
export const ownerSelectors = new EntitySelectorsFactory().create<Owner>('Owner');
type OwnerCollectionState = EntityCollection<Owner> & { field1: number, field2: string }; // just for convenient typing
const field1Selector = createSelector(ownerSelectors.selectCollection, (state: OwnerCollectionState) => state.field1);
I'm also looking for guidance on how to create custom actions and reducers with ngrx/data. The docs do seem to be incomplete and after a few days googling I still feel like I'm stumbling around in the dark somewhat.
I think my use case is fairly simple. I want to add a piece of additional collection state called selectedId for a particular entity type. This isn't a property of the entity itself, but of the entity collection.
I can add it to the entityMetadata as such:
export const entityMetadata: EntityMetadataMap = {
Todo: {
additionalCollectionState: {
selectedTodoId: '', <-- added okay
},
},
};
And sure enough it's added to the state. But how do I interact with it? I think I need a custom action, which I've also created:
export const setSelectedTodoId = createAction(
'[Todo] Set Selected Id',
props<{ selectedId: string }>()
);
Which can then be called:
setSelectedTodo(todo: Todo) {
this.dispatch(setSelectedTodoId({ selectedId: todo.id }));
}
And I see this action in the devtools. But how do I update the state? I think this is where a custom reducer would come in (??) but I don't know a) if that's right, and b) how to create one that extends the default.
I looked into a custom PersistentResultHandler but as this isn't a persistence concern (no call to the back end) I don't think that's the right approach.
Any help would be much appreciated, as would any pointers to existing documentation/articles or examples.
Haven't forgot about this one. Will take a closer look after v10 lands.
@brandonroberts Do you have any ETA for v10 release?
@hoang-innomizetech soon 🙂
Any news on this
@muhamedkarajic have you found a way to customize this.
I'm in the same boat here. I'm working on the front-end of an MYMP products with a particular api. For example, instead of
/api/resources
we have
/api/resources/:version
so I added an
additionalCollectionState: {
selectedVersion: '',
}
That I was planning on settingswith an action, and consuming somehow in the getAll() call.
But I cannot find anywhere an example of extending the reducer with a custom actions so I can update 'selectedVersion', or add an effect on this custom action
try this solution to solve pagination problem
example return from backend

store state

this solution is based on this doc
https://ngrx.io/guide/data/entity-metadata#additionalcollectionstate
_(my entity calls module, do not confuse with the keyword module)_
steps
1 - create additionalCollectionState on your EntityMetadataMap
import { EntityMetadataMap } from '@ngrx/data';
const moduleEntityMetadata: EntityMetadataMap = {
Module: {
additionalCollectionState: {
pageInfo: null
}
},
};
2 - overwrite 'PersistenceResultHandler' creating a new class
import { Action } from '@ngrx/store';
import { EntityAction, DefaultPersistenceResultHandler } from '@ngrx/data';
export class AdditionalPersistenceResultHandler extends DefaultPersistenceResultHandler {
handleSuccess(originalAction: EntityAction): (data: any) => Action {
const actionHandler = super.handleSuccess(originalAction);
return (data: any) => {
const action = actionHandler.call(this, data);
if (action && data && data.pageInfo) {
(action as any).payload.pageInfo = data.pageInfo;
(action as any).payload.data = data.data;
}
return action;
};
}
}
3 - overwrite 'EntityCollectionReducerMethods' creating a new class
import { EntityAction, EntityCollection, EntityDefinition, EntityCollectionReducerMethods } from '@ngrx/data';
export class AdditionalEntityCollectionReducerMethods<T> extends EntityCollectionReducerMethods<T> {
constructor(public entityName: string, public definition: EntityDefinition<T>) {
super(entityName, definition);
}
protected queryManySuccess(
collection: EntityCollection<T>,
action: EntityAction<T[]>
): EntityCollection<T> {
const ec = super.queryManySuccess(collection, action);
if ((action.payload as any).pageInfo) {
(ec as any).pageInfo = (action.payload as any).pageInfo;
}
return ec;
}
}
4 - Register AdditionalEntityCollectionReducerMethods, to do that, we need to create an AdditionalEntityCollectionReducerMethodFactory
import { Injectable } from "@angular/core";
import { EntityDefinitionService, EntityCollectionReducerMethodMap } from '@ngrx/data';
import { AdditionalEntityCollectionReducerMethods } from './entity-collection-reducer-methods';
@Injectable()
export class AdditionalEntityCollectionReducerMethodsFactory {
constructor(private entityDefinitionService: EntityDefinitionService) { }
create<T>(entityName: string): EntityCollectionReducerMethodMap<T> {
const definition = this.entityDefinitionService.getDefinition<T>(entityName);
const methodsClass = new AdditionalEntityCollectionReducerMethods(entityName, definition);
return methodsClass.methods;
}
}
5 - set providers in our main/store module
providers: [
{
provide: PersistenceResultHandler,
useClass: AdditionalPersistenceResultHandler
},
{
provide: EntityCollectionReducerMethodsFactory,
useClass: AdditionalEntityCollectionReducerMethodsFactory
},
]
We still need to find a way to customize the EntityOp and actions
additionalCollectionState: { pageInfo: null }
my values do not change
additionalCollectionState: { pageInfo: null }my values do not change
I used the variable 'pageInfo' as an example, if you want to use it as shown above, check if your api is returning the values in the same pattern and if all steps were correctly performed
@filipemansano
this protected method do not execute
```
protected queryManySuccess(
collection: EntityCollection
action: EntityAction
console.log('queryManySuccess', collection, action);
const ec = super.queryManySuccess(collection, action);
if ((action.payload as any).foo) {
// save the foo property from action.payload to entityCollection instance
(ec as any).foo = (action.payload as any).foo;
}
(ec as any).foo = 'mudei reducer';
return ec;
}```
additionalCollectionState: { pageInfo: null }my values do not change
I used the variable 'pageInfo' as an example, if you want to use it as shown above, check if your api is returning the values in the same pattern and if all steps were correctly performed
In my case EntityCollectionReducerMethods.queryManySuccess never run
fiexd: I changed .load to .getWithQuery :-D
@filipemansano how do you get this pageInfo values (selector) ?

@filipemansano how do you get this pageInfo values (selector) ?
entityService.selectors$.pageInfo$
https://ngrx.io/api/data/EntityCollectionServiceBase#selectors%24
@filipemansano how do you get this pageInfo values (selector) ?
entityService.selectors$.pageInfo$https://ngrx.io/api/data/EntityCollectionServiceBase#selectors%24
thanks.
It cant be subscribed ?
Hello everyone, long time passed. I just wanted to point out that you can use ngrx data for default setup and implement custom services, actions and reducers for more complex scenarious. From my experice so far its best to stick with the boilerplate until we dont have a proven concent. Hope this helps someone to avoid bad choices.
Most helpful comment
@muhamedkarajic if you feel that's better for you, feel free to use it. It's not acceptable however to call someone else's hard work crap. Be civil, or get blocked temporarily.