Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ngrx/entity and paginated collections #1000

Closed
codefactorydevelopment opened this issue Apr 28, 2018 · 7 comments
Closed

ngrx/entity and paginated collections #1000

codefactorydevelopment opened this issue Apr 28, 2018 · 7 comments

Comments

@codefactorydevelopment
Copy link

codefactorydevelopment commented Apr 28, 2018

I am not sure if i am missing something...

Just went through the example app and couldnt find an example/pattern for handling paginated collections (paginated by server).

If we imagine server API returns paginated collection and currently our store has 1st page with 10 entities (sorted alphabetically by the server by property name) all starting with letter a and now we want to create new entity with name zed.

Obviously, our newly created entity should not fall into collection we already have in store.
Same goes for update of entity thats not in state (for example from some search for single entity).

My point is, entity pattern seems to assume that, over time, we will have all entities in our store but i am sure i must be missing something about it.

So, is there some example or could someone share an idea how to incorporate ngrx/entity with paged data from server in some consistent way?

@MattiJarvinen
Copy link

Related to #687
Maybe something like https://stackoverflow.com/a/48296722/1740331 could fit as a state structure.

Also remember that when removing rows you would have to remove item from pages it's currently on. Same goes when adding new rows.

Even Redux´s Real world example https://github.com/reactjs/redux/blob/master/examples/real-world/src/actions/index.js#L102-L113 handles this by dispatching new page requests until there are no new pages. This isn't always an option when you really have lots of data.

Ionic used endless scrollable component to handle this back in Ionic 1. When virtual scrolled element hit the bottom there was this request more component which was placed in the bottom of the virtual scroll. When that component came into viewport it triggered an event to request more items. In this case pagination would be merely storing the possible next page request url / last loaded page number & page request loading state.

export interface TodoPaginationState {
  // Use this in the table pagination if needed
  currentPage: number;
  // Number of available pages from backend
  totalPages: number;
  // Last page loaded (ok this doesn't allow sparse loading 
  // in that case you'd need an array of loaded pages)
  lastLoadedPage: number;
  // If a page is being loaded
  pageLoading: boolean;
}

@codefactorydevelopment
Copy link
Author

@MattiJarvinen-BA

Thanks for your post & examples. Just went through them.

Please correct me if i'm mistaken, but does ngrx/entity, in general, make these 2 assumptions:

  1. That over time, all items will be loaded into store
  2. That model in list is the same as model in details (as returned from server; in other words, that server always returns full DTO representation of model)

are those 2 true?

Also, how does ngrx/entity go together with multi-user apps where we have multi-user environment where multiple users could upsert same collection of items? Whenever one user updates something, he practically invalidates client side states for all other users. Usually, that's the reason why we have guards for involved components where in CanActivate we get fresh collection (just current page).

So, from all said, i have a feeling that ngrx/entity is good for single-user environments (or multi-user but with conflicting changes very unlikely) and for collection with very limited set of items.

Please correct me if i'm mistaken....am i on the right track?

thanks!

@timdeschryver
Copy link
Member

timdeschryver commented May 3, 2018

IMHO ngrx entity is 'just' a tool to handle basic CRUD actions on a entity collection.

You  don't need to have all your entities in state, but you can do so if you want. It really depends on your use case. 

If you want a persistent state you should work with websockets. So that if your entity is changed on the sever, you can push these changes to every client or to a subset of clients. If it isn't business critical you could fetch the entities and update them in your client's state wherever/whenever you need, for example on a route change. To update or insert entities in batch you can use the upsert method. 

For the pagination part you can extend your state with for example a current page index, number of records on a page and a total count of all records. With this set up you can then do the pagination in the selectors. To have this in ngrx entity would be IMHO not ideal because pagination could be done is several ways, depending on the use case. 

If you want some help or feedback you should definitely check out the gitter channel

@tunecino
Copy link
Contributor

tunecino commented May 24, 2018

My way to handle it is to create 2 reducer files for each entity: item.ts and item-pagination.ts
The first one works as usual and puts any received entity into store (always using upsert) while the second looks a bit as follow:

export interface Page {
  key: number;
  dataIds: number[];
  query: ItemQuery;
  meta: HeadersMeta;
}

export interface State extends EntityState<Page> {
  currentDataIds: number[];
  currentQuery: ItemQuery;
  currentMeta: HeadersMeta;
  loaded: boolean;
  loading: boolean;
}

export const adapter: EntityAdapter<Page> = createEntityAdapter<Page>({
  selectId: (page: Page) => page.key,
  sortComparer: false
});

export const initialQuery: ItemQuery = {
  page: 1,
  'per-page': 20,
  expand: 'brand',
  fields: null,
  sort: '-id',
  filter: undefined
};

export const initialState: State = adapter.getInitialState({
  currentDataIds: [],
  currentQuery: initialQuery,
  currentMeta: {},
  loaded: false,
  loading: false
});

Each time I do a request to server, I use a function to generate a key (or hash) from that ItemQuery and I use it to store the related response (only ids like: dataIds: [2, 5, 9, 10]) and meta data.

Then when I do the same request one more time, after generating its key, if I find I already have it in item-pagination.ts related store, I only load meta and dataIds from there and my selectors will use those ids to load or filter related entities from the first store (item.ts).

And of corse, that caching technique is useless if you don't have other services that knows about changes when they occur on server and properly update everything.

@SwatiSinha0693
Copy link

SwatiSinha0693 commented Aug 22, 2020

@tunecino can you please provide any link of your code.It would be really helpful.

@chan-dev
Copy link

@tunecino I agree with @SwatiSinha0693, if you could provide relevant code snippets that would be helpful

@peimelo
Copy link

peimelo commented Dec 17, 2020

Hello guys, I have the same question posed by @codefactorydevelopment and I did it in my project in a way that worked for me:

https://github.com/peimelo/controlled-health-frontend/tree/master/src/app/weights

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants