Store Features
Generates necessary state, computed and methods for remote infinite scroll pagination of entities in the store. This is ideal for implementing infinite scroll where the entities cache keeps growing, or for a paginated list that only allows going to the next and previous page because you dont know the total number of entities probably because the data is top big and partitioned in multiple nodes.
When the page changes, it will try to load the current page from cache if it's not present, it will call set[Collection]Loading(), and you should either create an effect that listens to is[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. Requires withEntities and withCallStatus to be used.
The generated set[Collection]Result method will append the entities to the cache of entities, it requires either just set of requested entities set[Collection]Result({ entities }) in which case it will assume there is no more result if you set less entities than the requested buffer size, or you can provide an extra param to the entities, total set[Collection]Result({ entities, total }) so it calculates if there is more or a hasMore param set[Collection]Result({entities, hasMore}) that you can set to false to indicate the end of the entities.
Requires withEntities and withCallStatus to be present in the store.
In this example we have a list of products and we want the to get more pages while you scroll.
const entityConfig = entityConfig({
entity: type<T>(),
collection: "products"
});
export const store = signalStore(
// required withEntities and withCallStatus
withEntities(entityConfig),
withCallStatus({ prop: collection, initialValue: 'loading' }),
withEntitiesRemoteScrollPagination({
...entityConfig,
pageSize: 5,
pagesToCache: 2,
}),
// after you can use withEntitiesLoadingCall to connect the filter to
// the api call, or do it manually as shown after
withEntitiesLoadingCall({
...entityConfig,
fetchEntities: ({ productsPagedRequest }) => {
return inject(ProductService)
.getProducts({
take: productsPagedRequest().size,
skip: productsPagedRequest().startIndex,
}).pipe(
map((d) => ({
entities: d.resultList,
total: d.total,
})),
)
},
}),
);
In your component you will need a data source if using angular cdk scroll directive
// in your component add
store = inject(ProductsRemoteStore);
dataSource = getInfiniteScrollDataSource(store, { collection: 'products' }) // pass this to your cdkVirtualFor see examples section
// pass the dataSource to your cdkVirtualFor
Then in your template:
<cdk-virtual-scroll-viewport
itemSize="42"
class="fact-scroll-viewport"
minBufferPx="200"
maxBufferPx="200"
>
<mat-list>
<mat-list-item
*cdkVirtualFor="let item of dataSource; trackBy: trackByFn"
>{{ item.name }}</mat-list-item
>
</mat-list>
</cdk-virtual-scroll-viewport>
You can mix this feature with other remote store features like withEntitiesRemoteSort, withEntitiesRemoteFilter, etc.
const productsStoreFeature = signalStoreFeature(
withEntities({
entity: productsEntity,
collection: productsCollection,
}),
withCallStatus({
initialValue: 'loading',
collection: productsCollection,
errorType: type<string>(),
}),
withEntitiesRemoteFilter({
entity: productsEntity,
collection: productsCollection,
defaultFilter: { search: '' },
}),
withEntitiesRemoteScrollPagination({
...entityConfig,
pageSize: 5,
pagesToCache: 2,
}),
withEntitiesRemoteSort({
entity: productsEntity,
collection: productsCollection,
defaultSort: { field: 'name', direction: 'asc' },
}),
withEntitiesLoadingCall(
({ productsPagedRequest, productsFilter, productsSort }) => ({
collection: productsCollection,
fetchEntities: async () => {
const res = await lastValueFrom(
inject(ProductService).getProducts({
search: productsFilter().search,
skip: productsPagedRequest().startIndex,
take: productsPagedRequest().size,
sortAscending: productsSort().direction === 'asc',
sortColumn: productsSort().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.
Property | Description | Value |
---|---|---|
entity | The entity type | type<T>() |
collection | The name of the collection. Optional | string |
selectId | The function to use to select the id of the entity | SelectEntityId<Entity> |
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 |
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 } }>;
Generates the following computed signals
entitiesCurrentPage: Signal<{ 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: Signal<{ entities: Product[], pageIndex: number, total: number, pageSize: 5, pagesCount: number, hasPrevious: boolean, hasNext: boolean, isLoading: boolean }>;
usersPagedRequest: Signal<{ startIndex: number, size: number, page: number }>;
Generates the following methods
// loads the page and sets the requestPage to the pageIndex
loadEntitiesNextPage: () => void;
// loads previous page
loadEntitiesPreviousPage: () => void;
// loads more entities (used for infinite scroll datasource)
store.loadMoreEntities()
// appends the entities to the cache of entities sets the total
setEntitiesPagedResult:(entities: User[], total?: number, hasMore?: boolean) => void;
If collection provided, the following methods are generated, example: users
// loads the page and sets the requestPage to the pageIndex
loadUsersNextPage: () => void;
// loads previous page
loadUsersPreviousPage: () => void;
// loads more entities (used for infinite scroll datasource)
store.loadMoreUsers()
// appends the entities to the cache of entities sets the total
setUsersPagedResult:(entities: User[], total?: number, hasMore?: boolean) => void;