Store Features

withEntitiesRemotePagination

Generates necessary state, computed and methods for remote pagination of entities in the store. Call load[Collection]Page to change the page, it will try to load the new page from cache if it's not present, it will call set[Collection]Loading(), and you should either create an effect that listens to [Collection]Loading and call the api with the [Collection]PagedRequest params and use set[Collection]Result to set the result and changing the status errors manually, or use withEntitiesLoadingCall to call the api with the [Collection]PagedRequest params which handles setting the result and errors automatically.

In case you don't want load[Collection]Page to call set[Collection]Loading() (which triggers a fetchEntities), you can pass skipLoadingCall: true to load[Collection]Page. Useful in cases where you want to further change the state before manually calling set[Collection]Loading() to trigger a fetch of entities.

This will keep at least the provided (pagesToCache) pages in memory, so previous pages could be removed from the cache. If you need to keep all previous pages in memory, use withEntitiesRemoteScrollPagination instead.

Requires withEntities and withCallStatus to be present in the store.

Examples

Paginating a list of entities

In this example we have a list of products and we want to paginate them, we will use the pageSize prop to set the initial page size.

const productEntityConfig = entityConfig({
  entity: type<T>(),
  collection: 'product'
});

export const store = signalStore(
  withEntities(productEntityConfig),
  withCallStatus(productEntityConfig, { initialValue: 'loading' }),
  withEntitiesRemotePagination(productEntityConfig, {
    pageSize: 5,
    pagesToCache: 2,
  }),
  withEntitiesLoadingCall(productEntityConfig, ({ productEntitiesPagedRequest }) => ({
    fetchEntities: () => {
      return inject(ProductService)
        .getProducts({
          take: productEntitiesPagedRequest().size,
          skip: productEntitiesPagedRequest().startIndex,
        })
        .pipe(
          map((d) => ({
            entities: d.resultList,
            total: d.total,
          })),
        );
    },
  })),
);

Now we use it in a component, the generated usersCurrentPage signal will provide the entities for the current page, the pageIndex and the total number of entities:

@for (user of store.productEntitiesCurrentPage.entities(); track user.id){
  {{ user.name }}
}
<mat-paginator
  [pageSizeOptions]="[5, 10, 25, 100]"
  [length]="store.productEntitiesCurrentPage.total()"
  [pageSize]="store.productEntitiesCurrentPage.pageSize()"
  [pageIndex]="store.productEntitiesCurrentPage.pageIndex()"
  (page)="store.loadProductEntitiesPage($event)"
></mat-paginator>

Mixing with other remote store features

You can mix this feature with other remote store features like withEntitiesRemoteSort, withEntitiesRemoteFilter, etc.

const productsEntityConfig = entityConfig({
  entity: productsEntity,
  collection: productsCollection,
});

const productsStoreFeature = signalStoreFeature(
  withEntities(productsEntityConfig),
  withCallStatus(productsEntityConfig, {
    initialValue: 'loading',
    errorType: type<string>(),
  }),
  withEntitiesRemoteFilter(productsEntityConfig, {
    defaultFilter: { search: '' },
  }),
  withEntitiesRemotePagination(productsEntityConfig, {
    pageSize: 10,
  }),
  withEntitiesRemoteSort(productsEntityConfig, {
    defaultSort: { field: 'name', direction: 'asc' },
  }),
  withEntitiesLoadingCall(
    productsEntityConfig,
    ({ productEntitiesPagedRequest, productEntitiesFilter, productEntitiesSort }) => ({
      fetchEntities: async () => {
        const res = await lastValueFrom(
          inject(ProductService).getProducts({
            search: productEntitiesFilter().search,
            skip: productEntitiesPagedRequest().startIndex,
            take: productEntitiesPagedRequest().size,
            sortAscending: productEntitiesSort().direction === 'asc',
            sortColumn: productEntitiesSort().field,
          }),
        );
        return { entities: res.resultList, total: res.total };
      },
    }),
  ),
);

To know more how it mixes and works with other local store features, check Working with Entities section.

Using it with the Angular Resource view

There is no separate resource for a page. [collection]EntitiesCurrentPage already carries the entities, total, pageIndex, pageSize and isLoading, and being a DeepSignal each prop can be read on its own (productEntitiesCurrentPage.total()). Pair it with the resource generated by withEntitiesLoadingCall when you want status() and error(): the rows and the paginator come from the page, since the entity collection only holds the cached page window.

API Reference

Property Description Value
entity The entity type type()
collection The name of the collection. Optional string
selectId The function to use to select the id of the entity SelectEntityId
pageSize The number of entities to show per page number
currentPage The current page to show number
pagesToCache The number of pages to cache number

State

Generates the following signals

pagination: Signal<{ currentPage: number; requestPage: number; pageSize: 5; total: number; pagesToCache: number; cache: { start: number; end: number } }>;

If collection provided, the following signals are generated, example: users

usersPagination: Signal<{ currentPage: number; requestPage: number; pageSize: 5; total: number; pagesToCache: number; cache: { start: number; end: number } }>;

Computed

Generates the following computed signals, they are DeepSignals, so each prop can be read as a signal of its own, e.g. entitiesCurrentPage.total()

entitiesCurrentPage: DeepSignal<{ entities: Product[]; pageIndex: number; total: number; pageSize: 5; pagesCount: number; hasPrevious: boolean; hasNext: boolean; isLoading: boolean }>;
entitiesPagedRequest: Signal<{ startIndex: number; size: number; page: number }>;

If collection provided, the following computed signals are generated, example: users

usersCurrentPage: DeepSignal<{ entities: Product[]; pageIndex: number; total: number; pageSize: 5; pagesCount: number; hasPrevious: boolean; hasNext: boolean; isLoading: boolean }>;
usersPagedRequest: Signal<{ startIndex: number; size: number; page: number }>;

Methods

Generates the following methods

// loads the page and sets the requestPage to the pageIndex
loadEntitiesPage: (options:{ pageIndex: number, forceLoad?: boolean, skipLoadingCall?:boolean }) => void;
// appends the entities to the cache of entities sets the total
setEntitiesPagedResult:(entities: User[], total: number) => void;

If collection provided, the following methods are generated, example: users

// loads the page and sets the requestPage to the pageIndex
loadUserPage: (options:{ pageIndex: number, forceLoad?: boolean, skipLoadingCall?:boolean }) => void;
// appends the entities to the cache of entities sets the total
setUserPagedResult:(entities: User[], total: number) => void;