Set of prebuilt ngrx signals Custom Store Features that solve common problems such as adding pagination, sorting, filtering, selection of entities, and more.
- ✅ Reduce boilerplate with generated strongly typed signals and methods.
- ✅ Store Feature to load entities list
- ✅ Store Feature to create a status for backend operations
- ✅ Store Feature to filter remote and locally entities list
- ✅ Store Feature to sort remote and locally entities list
- ✅ Store Feature to paginate entities list locally or remotely
- ✅ Store Feature to create a infinite scroll pagination
- ✅ Store Feature to add single or multi selection entities list
- ✅ Store Feature to reduce boilerplate needed when calling backend apis
- ✅ Store Feature to sync the state to local or session storage
- ✅ Caching
Besides angular, you will need to have ngrx/signals installed with this lib you can do so with:
npm i @ngrx/signals --save
Install @ngrx-traits/signals
npm i @ngrx-traits/signals --save
To use this library you will first need to understand some of the concepts of ngrx/signals, you can find more about it here.
To better understand what the library does, let's take a look at a simple example.
const entity = type<Product>();
export const ProductsLocalStore = signalStore(
withEntities({ entity }),
withCallStatus({ initialValue: 'loading' }),
// 👆 adds signals isLoading(), isLoaded(), error()
// and methods setLoading() setLoaded(), setError(error)
withEntitiesLocalPagination({ entity, pageSize: 5 }),
// 👆 adds signal entitiesCurrentPage()
// and method loadEntitiesPage({pageIndex: number})"
withHooks(({ setLoaded, setError, ...store }) => ({
onInit: async () => {
const productService = inject(ProductService);
try {
const res = await lastValueFrom(productService.getProducts());
patchState(store, setAllEntities(res.resultList));
setLoaded();
} catch (e) {
setError(e);
}
},
})),
);
In the example, we use the withCallStatus
store feature, which adds computed signals like isLoading() and isLoaded() and corresponding setters setLoading and setLoading. You can see them being used in the withHooks to load the products.
You can also see in the example withEntitiesLocalPagination
, which will add signal entitiesCurrentPage() and loadEntitiesPage({pageIndex: number}) that we can use to render a paginated list like the one below.
@if (store.isLoading()) {
<mat-spinner />
} @else {
<div>
<mat-list>
<!-- 👇 we use store.entitiesCurrentPage().entities
instead of store.entities() ↓ -->
@for (
product of store.entitiesCurrentPage().entities;
track product.id
) {
<mat-list-item>{{ product.name }}</mat-list-item>
}
</mat-list>
<!-- 👇 entitiesCurrentPage has all the props
needed for the paginator, and loadEntitiesPage
handles page changes -->
<mat-paginator
[length]="store.entitiesCurrentPage().total"
[pageSize]="store.entitiesCurrentPage().pageSize"
[pageIndex]="store.entitiesCurrentPage().pageIndex"
(page)="store.loadEntitiesPage($event)"
/>
</div>
}
`,
})
export class SignalProductListPaginatedPageContainerComponent {
store = inject(ProductsLocalStore);
}
Most store features support a collection param that allows you have custom names in the generated signals and methods for example:
const entity = type<Product>();
const collection = 'products';
export const ProductsLocalStore = signalStore(
withEntities({ entity , collection}),
withCallStatus({ collection, initialValue: 'loading' }),
// 👆 adds signals isProductsLoading(), isProductsLoaded(), productsError()
// and methods setProductsLoading() setProductsLoaded(), setProductsError(error)
withEntitiesLocalPagination({ entity, collection, pageSize: 5 }),
// 👆 adds signal productsCurrentPage()
// and method loadProductsPage({pageIndex: number})"
withHooks(({ setProductsLoaded, setProductsError, ...store }) => ({
onInit: async () => {
const productService = inject(ProductService);
try {
const res = await lastValueFrom(productService.getProducts());
patchState(store, setAllEntities(res.resultList));
setProductsLoaded();
} catch (e) {
setProductsError(e);
}
}
}))
);
To see a full list of the store features in the library with details and examples, check the API documentation.
Also, I recommend checking the example section, where you can see multiple use cases for the library. Examples
For a more in step by step guide you can see the following articles.