### Start Examples App Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/CONTRIBUTING.md Start the example application using pnpm. ```bash pnpm start ``` -------------------------------- ### Clone and Run NgRx Traits Examples Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/example-app/src/app/examples/ngrx/README.md Follow these commands to clone the repository, install dependencies, and start the project to view the examples in your browser. ```bash git clone https://github.com/gabrielguerrero/ngrx-traits.git cd ngrx-traits npm i npm start ``` -------------------------------- ### Clone and Run NgRx Traits Examples Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/example-app/src/app/examples/README.md Follow these steps to clone the repository, install dependencies using pnpm, and start the development server to view the examples in your browser. ```bash git clone https://github.com/gabrielguerrero/ngrx-traits.git cd ngrx-traits pnpm install pnpm start ``` -------------------------------- ### Start Docs App Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/CONTRIBUTING.md Start the documentation application using pnpm. ```bash pnpm start-docs ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/CONTRIBUTING.md Install all project dependencies using pnpm. This should be run from the root folder of the project. ```bash pnpm install ``` -------------------------------- ### withCallStatus and withEntitiesLoadingCall Example Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-call-status.md Demonstrates using `withCallStatus` in conjunction with `withEntitiesLoadingCall` for automatic data fetching on initialization. ```APIDOC ## withCallStatus and withEntitiesLoadingCall Example ### Description This example showcases how `withCallStatus` can be used with `withEntitiesLoadingCall`. When `withCallStatus` is initialized with `initialValue: 'loading'`, `withEntitiesLoadingCall` automatically triggers the `fetchEntities` function. ### Method `signalStore` ### Endpoint N/A (This is a NgRx trait, not a REST endpoint) ### Parameters #### Request Body - **`withEntities({ entity, collection })`** (function) - Manages entity state. - **`withCallStatus({ prop: collection, initialValue: 'loading' })`** (function) - Manages call status, setting initial state to 'loading'. - **`withEntitiesLoadingCall({ collection, fetchEntities })`** (function) - Triggers `fetchEntities` when the status is 'loading'. - **`fetchEntities`** (function) - A function that returns an Observable to fetch entities. ### Request Example ```typescript import { signalStore, withEntities, withCallStatus, withEntitiesLoadingCall } from '@ngrx-traits/signals'; import { ProductService } from './product.service'; // Assuming ProductService exists import { inject } from '@angular/core'; import { map } from 'rxjs/operators'; import { Product } from './product.model'; // Assuming Product model exists import { type } from '@angular/core'; const entity = type(); const collection = 'product'; export const ProductsRemoteStore = signalStore( { providedIn: 'root' }, withEntities({ entity, collection }), withCallStatus({ prop: collection, initialValue: 'loading' }), withEntitiesLoadingCall({ collection, fetchEntities: () => { return inject(ProductService) .getProducts() .pipe( map((d) => ({ entities: d.resultList, total: d.total, })), ); }, })); ``` ### Response #### Success Response (N/A) This combination of traits automatically fetches data on store initialization and updates the entity and call status states. #### State Generated - `productEntities`: Entity state for products. - `productCallStatus`: Call status for product operations. - `isProductLoading`, `isProductLoaded`, `productError`: Computed signals for product call status. - Data fetched by `fetchEntities` is loaded into the store. ``` -------------------------------- ### Install Angular CLI Globally Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/CONTRIBUTING.md Install the Angular CLI globally using pnpm. This is required for local configuration. ```bash pnpm add -g @angular/cli ``` -------------------------------- ### Example: Mixing with other local store features Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-local-sort.md Illustrates how to combine withEntitiesLocalSort with other local store features like filtering and pagination. ```APIDOC ## Example: Mixing with other local store features You can mix this feature with other local store features like withEntitiesLocalFilter, withEntitiesLocalPagination, etc. ```typescript const productsEntityConfig = entityConfig({ entity: type(), collection: 'product', }); export const ProductsLocalStore = signalStore( { providedIn: 'root' }, withEntities(productsEntityConfig), withCallStatus({ ...productsEntityConfig, initialValue: 'loading' }), withEntitiesLocalPagination({ ...productsEntityConfig, pageSize: 5, }), withEntitiesLocalFilter({ ...productsEntityConfig, defaultFilter: { search: '' }, filterFn: (entity, filter) => !filter?.search || entity?.name.toLowerCase().includes(filter?.search.toLowerCase()), }), withEntitiesLocalSort({ ...productsEntityConfig, defaultSort: { field: 'name', direction: 'asc' }, }), withEntitiesLoadingCall({ ...productsEntityConfig, fetchEntities: () => { return inject(ProductService) .getProducts() .pipe(map((d) => d.resultList)); }, }), ); ``` To know more how it mixes and works with other local store features, check [Working with Entities](/docs/getting-started/working-with-entities) section. ``` -------------------------------- ### Install @ngrx/signals and @ngrx-traits/signals Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/docs/signals.md Install the necessary Ngrx/signals and @ngrx-traits/signals packages using npm or yarn. ```bash npm i @ngrx/signals --save ``` ```bash yarn add @ngrx/signals ``` ```bash npm i @ngrx-traits/signals --save ``` ```bash yarn add @ngrx-traits/signals ``` ```bash npm i @ngrx-traits/signals --save ``` -------------------------------- ### Install @ngrx/signals with Npm Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/getting-started/installation.md Use this command to install the @ngrx/signals package via npm. ```bash npm i @ngrx/signals --save ``` -------------------------------- ### Sync Store to Session Storage Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-sync-to-web-storage.md Example of syncing the entire store state to session storage using a specified key. ```typescript const store = signalStore( // following are not required, just an example it can have anything withEntities({ entity, collection }), withCallStatus({ prop: collection, initialValue: 'loading' }), // Sync the store to session storage withSyncToWebStorage({ key: 'my-key', type: 'session', // type: 'local', // to use local storage }), ); ``` -------------------------------- ### Example: Splitting State with Multiple `withSyncToWebStorage` Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-sync-to-web-storage.md Demonstrates how to use multiple `withSyncToWebStorage` instances to manage different parts of the application state independently, each with its own storage key. ```APIDOC ```typescript import { signalStoreFeature, type } from '@ngrx/signals'; import { entityConfig, withEntities } from '@ngrx/entity'; import { withCallStatus } from '@ngrx/operators'; import { withSyncToWebStorage } from 'ngrx-store-localstorage'; // Define Product type and Order type (assuming they are defined elsewhere) interface Product {} interface Order {} // Custom store feature for products list function withProductsList() { const productsEntityConfig = entityConfig({ entity: type(), collection: 'product', }); return signalStoreFeature( withEntities(productsEntityConfig), withCallStatus(productsEntityConfig), withSyncToWebStorage({ key: 'my-products', type: 'session', restoreOnInit: true, saveStateChangesAfterMs: 300, expires: 1000 * 60 * 60 * 12, // 12 hours }), ); } // Custom store feature for orders list function withOrderList() { const ordersEntityConfig = entityConfig({ entity: type(), collection: 'order', }); return signalStoreFeature( withEntities(ordersEntityConfig), withCallStatus(ordersEntityConfig), withSyncToWebStorage({ key: 'my-orders', type: 'session', restoreOnInit: true, saveStateChangesAfterMs: 300, expires: 1000 * 60 * 60 * 12, // 12 hours }), ); } // Combine features into a single store const store = signalStore( withProductsList(), withOrderList() ); ``` ``` -------------------------------- ### Install @ngrx-traits/signals with Yarn Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/getting-started/installation.md Use this command to install the @ngrx-traits/signals package via yarn. ```bash yarn add @ngrx-traits/signals ``` -------------------------------- ### Install @ngrx-traits/signals with Npm Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/getting-started/installation.md Use this command to install the @ngrx-traits/signals package via npm. ```bash npm i @ngrx-traits/signals --save ``` -------------------------------- ### Complex Example: Entities, Filtering, Pagination, Sorting, and SSR Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-server-state-transfer.md A real-world example demonstrating the integration of multiple NgRx Traits including entities, filtering, pagination, sorting, and SSR state transfer. ```APIDOC ## Complex Example with Multiple Features ### Description This example showcases a comprehensive NgRx store setup that combines entities, local pagination, local filtering, local sorting, single selection, route query parameter synchronization, and server-side state transfer. ### Store Configuration ```typescript const productsEntityConfig = entityConfig({ entity: type(), collection: 'product', }); export const ProductsSSRStore = signalStore( { providedIn: 'root' }, // 1. State declarations first withEntities(productsEntityConfig), withCallStatus({ ...productsEntityConfig, initialValue: 'loading' }), // 2. Feature configurations withEntitiesLocalPagination({ ...productsEntityConfig, pageSize: 5, }), withEntitiesLocalFilter({ ...productsEntityConfig, defaultFilter: { search: '' }, filterFn: (entity, filter) => !filter?.search || entity?.name.toLowerCase().includes(filter?.search.toLowerCase()), }), withEntitiesLocalSort({ ...productsEntityConfig, defaultSort: { field: 'name', direction: 'asc' }, }), withEntitiesSingleSelection(productsEntityConfig), // 3. Sync to URL query params withEntitiesSyncToRouteQueryParams(productsEntityConfig), // 4. Transfer state from server to client (BEFORE loading call) withServerStateTransfer({ key: 'product-list-ssr', }), // 5. Loading call LAST - when state comes from server // overrides status to loaded so fetchEntities wont be trigger withEntitiesLoadingCall({ ...productsEntityConfig, fetchEntities: () => { return inject(ProductService) .getProducts() .pipe(map((d) => d.resultList)); }, }), ); ``` ### How it works **On the Server:** - An effect monitors store state changes. - State is serialized and saved to Angular's `TransferState`. - Serialized state is embedded in the HTML sent to the client. **On the Client:** - During hydration, state is read from `TransferState`. - State is restored to the store using `patchState` (or a custom mapper). - The `onRestore` callback is executed if provided. - `TransferState` is cleaned up to free memory. ``` -------------------------------- ### withCallStatus and withMethods Example Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-call-status.md Combines `withCallStatus` with `withMethods` to handle backend calls, including setting loading and error states. ```APIDOC ## withCallStatus and withMethods Example ### Description This example demonstrates integrating `withCallStatus` with `withMethods` to manage asynchronous operations. It shows how to trigger loading states, handle responses, and catch errors, updating the store accordingly. ### Method `signalStore` ### Endpoint N/A (This is a NgRx trait, not a REST endpoint) ### Parameters #### Request Body - **`withEntities({ entity, collection })`** (function) - Manages entity state. - **`withCallStatus({ collection, initialValue })`** (function) - Manages call status. - **`withMethods(({ ... }) => ({ loadProducts }))`** (function) - Defines custom methods for the store. - **`loadProducts`** (function) - A method that triggers the product fetching logic. ### Request Example ```typescript import { signalStore, withEntities, withCallStatus, withMethods } from '@ngrx-traits/signals'; import { ProductService } from './product.service'; // Assuming ProductService exists import { inject } from '@angular/core'; import { pipe, switchMap, tap, catchError } from 'rxjs'; import { EMPTY } from 'rxjs'; import { Product } from './product.model'; // Assuming Product model exists import { type } from '@angular/core'; import { patchState, setAllEntities } from '@ngrx/signals/entities'; const entity = type(); const collection = 'product'; export const ProductsRemoteStore = signalStore( { providedIn: 'root' }, withEntities({ entity, collection }), withCallStatus({ collection, initialValue: 'loading' }), withMethods(({ setProductEntitiesLoading, setProductEntitiesLoaded, setProductEntitiesError, ...store }) => ({ loadProducts: rxMethod(pipe( switchMap(() => { setProductEntitiesLoading(); return inject(ProductService) .getProducts() .pipe( tap((res) => patchState( store, setAllEntities(res.resultList, { collection: 'product' }), ), ), catchError((error) => { setProductEntitiesError(error); return EMPTY; })); })) ) })) ); ``` ### Response #### Success Response (N/A) This combination of traits modifies the store's state and defines methods for interaction, not a direct response. #### State Generated - `productEntities`: Entity state for products. - `productCallStatus`: Call status for product operations. - `isProductLoading`, `isProductLoaded`, `productError`: Computed signals for product call status. - `loadProducts`: A method to trigger the loading of products. ``` -------------------------------- ### Example: Implement remote sort for a list of entities Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-local-sort.md Demonstrates how to implement remote sorting for a list of entities using the withEntitiesLocalSort trait with a dropdown. ```APIDOC ## Example: Implement remote sort for a list of entities ```typescript const entity = entityConfig({ entity: type(), collection: 'user' }) const store = signalStore( withEntities(entity), withEntitiesLocalSort({ ...entity, defaultSort: {field: 'name', direction: 'asc'} }) ); ``` To use you generally need either a sort dropdown if is a list or a table where clicking on the columns headers sorts, bellow is how s used with a dropdown (you can find full source code in the examples folder in GitHub): ```html ... show products list ``` If you manually changed the entities and want to reapply the sort you can call the sort entities method without any param to reapply the last sort ``` -------------------------------- ### Store Setup with mapError Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-calls.md Configure your store with `withCalls` and `mapError` to properly type and handle errors from API calls. ```typescript const RegisterUserStore = signalStore( withCalls(() => ({ registerUser: callConfig({ call: (params: { name: string; email: string; password: string }) => inject(UserService).register(params), mapError: (error) => { return (error as HttpErrorResponse).error.message; }, }), })), ); ``` -------------------------------- ### Configure Local Store with Multiple Features Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-hybrid-filter.md This example shows how to configure a local store by mixing `withEntities`, `withCallStatus`, `withEntitiesLocalPagination`, `withEntitiesHybridFilter`, `withEntitiesLocalSort`, and `withEntitiesLoadingCall`. Ensure remote features are not mixed with local pagination to avoid issues. ```typescript const productsEntityConfig = entityConfig({ entity: type(), collection: 'product', }); export const ProductsLocalStore = signalStore( { providedIn: 'root' }, withEntities(productsEntityConfig), withCallStatus({ ...productsEntityConfig, initialValue: 'loading' }), withEntitiesLocalPagination({ ...productsEntityConfig, pageSize: 5, }), withEntitiesHybridFilter({ entity, defaultFilter: { search: '', categoryId: 'snes' }, isRemoteFilter: (previous, current) => previous.categoryId !== current.categoryId, // only remote filter when the category changes filterFn: (entity, filter) => !filter?.search || entity?.name.toLowerCase().includes(filter?.search.toLowerCase()), }), withEntitiesLocalSort({ ...productsEntityConfig, defaultSort: { field: 'name', direction: 'asc' }, }), withEntitiesLoadingCall({ ...entityConfig, fetchEntities: ({ productEntitiesFilter }) => { return inject(ProductService).getProducts({ categoryId: productEntitiesFilter().categoryId, }); }, }), ); ``` -------------------------------- ### withServerStateTransfer: Sync State with TransferState for SSR (Value Mapper Example) Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/api-docs.md This example shows how to use withServerStateTransfer with a custom valueMapper for Server-Side Rendering. The valueMapper allows for custom transformations between the store's state and the data transferred via TransferState. ```typescript const store = signalStore( withState({ userProfile: { userName: '', email: '', preferences: { theme: 'light', notifications: true }, tempData: null, } }), withServerStateTransfer({ key: 'user-profile', // Custom mapper to transfer only userName and email valueMapper: (store) => ({ stateToTransferValue: () => ({ userName: store.userProfile().userName, email: store.userProfile().email, }), transferValueToState: (savedData) => { patchState(store, { userProfile: { ...store.userProfile(), userName: savedData.userName, email: savedData.email, } }); }, }), }), ); ``` -------------------------------- ### Adding Multi Selection to a List Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-multi-selection.md Example demonstrating how to add multi-selection capabilities to a list of entities using `withEntities` and `withEntitiesMultiSelection`. ```APIDOC ## Example: Adding multi selection to a list ### Setup ```typescript import { signalStore, withEntities } from '@ngrx/signals'; import { withEntitiesMultiSelection } from '@ngrx-traits/signals'; import { entityConfig, type } from '@ngrx-traits/core'; interface Product { id: number; label: string; } const entity = entityConfig({ entity: type(), collection: 'product' }); const store = signalStore( withEntities(entity), withEntitiesMultiSelection(entity) ); ``` ### Usage in Template ```html @for (item of store.productEntities(); track item.id) { {{ item.label }} } ``` ``` -------------------------------- ### Create a Product Local Store with Ngrx Traits Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/docs/signals.md Example of creating a signal store using various @ngrx-traits/signals features like withEntities, withCallStatus, withEntitiesLocalPagination, and withCalls. The onInit hook is used to load initial data. ```typescript const entity = type(); 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); } }, })), withCalls(() => ({ loadProductDetail: ({ id }: { id: string }) => inject(ProductService).getProductDetail(id), })), // 👆 adds signals isLoadProductDetailLoading(), loadProductDetailResult() // and method loadProductDetail({id}) ); ``` -------------------------------- ### Basic Usage: Transfer Entire Store State Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-server-state-transfer.md This example demonstrates the basic usage of withServerStateTransfer to transfer the entire store state using a specified key. ```typescript const store = signalStore( withEntities({ entity, collection }), withCallStatus({ prop: collection, initialValue: 'loading' }), withServerStateTransfer({ key: 'my-state', }), ); ``` -------------------------------- ### Basic CacheRxCall Example Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/getting-started/caching.md Demonstrates basic caching for an observable call using cacheRxCall. The cache will expire after 5 minutes. ```typescript withCalls( ( service = inject(ProductService), ) => ({ loadProductDetail: ({ id }: { id: string }) => cacheRxCall({ key: ['products', id], call: service.getProductDetail(id), expires: 1000 * 60 * 5 // 5 mins }), }), ) ``` -------------------------------- ### Define Custom Load Product Trait Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/common/src/lib/custom-traits.md This example demonstrates how to create a custom trait for loading product details. It defines actions, selectors, initial state, reducer, and effects for the trait. ```typescript import { TraitActionsFactoryConfig, TraitInitialStateFactoryConfig, } from '@ngrx-traits/core'; interface ProductDetail { id: string; name: string; description: string; maker: string; price: number; releaseDate: string; image: string; } interface SelectedProductState { selectedProduct: ProductDetail; } export function addLoadProductTrait() { const initialState: SelectedProductState = { selectedProduct: null }; return createTraitFactory({ key: 'loadProduct', depends: [], config: {}, actions: ({ actionsGroupKey }: TraitActionsFactoryConfig) => ({ loadProduct: createAction( `${actionsGroupKey} Load Product`, props<{ id: string }>() ), loadProductSuccess: createAction( `${actionsGroupKey} Load Product Success`, props<{ product: ProductDetail }>() ), loadProductFail: createAction(`${actionsGroupKey} Load Product Fail`), }), selectors: () => ({ selectProduct: (state: SelectedProductState) => state.selectedProduct, }), initialState: ({ previousInitialState }: TraitInitialStateFactoryConfig) => (previousInitialState ?? {}) as SelectedProductState, reducer: (initialState, allActions, allSelectors) => createReducer( initialState, on(allActions.loadProductSuccess, (state, { product }) => ({ ...state, selectedProduct: product, })) ), effects: (allActions, allSelectors, allConfigs) => { @Injectable() class LoadProductEffect extends TraitEffect { loadProduct$ = createEffect(() => { return this.actions$.pipe( ofType(allActions.loadProduct), // call backend... map(({ id }) allActions.loadProductSuccess({ product: { id, name: 'sss' } }) ) ); }); } return [LoadProductEffect]; }, }); } ``` -------------------------------- ### Using withCallStatus and withEntitiesLoadingCall Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-call-status.md Combines withCallStatus with withEntitiesLoadingCall to automatically fetch entities when the status is set to loading. This example initializes the store with a loading state, triggering the fetch on initialization. ```typescript const entity = type(); const collection = 'product'; export const ProductsRemoteStore = signalStore( { providedIn: 'root' }, withEntities({ entity, collection }), withCallStatus({ prop: collection, initialValue: 'loading' }), // withEntitiesLoading will Call the fetch function when the status is set to loading // in this case it will happen on init because the initial value is loading withEntitiesLoadingCall({ collection, fetchEntities: () => { return inject(ProductService) .getProducts() .pipe( map((d) => ({ entities: d.resultList, total: d.total, })), ); }, }); ``` -------------------------------- ### Local Filtering, Sorting, and Pagination with Entity Loading Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/getting-started/working-with-entities.md This example demonstrates combining `withEntitiesLoadingCall` with local manipulation traits (`withEntitiesLocalPagination`, `withEntitiesLocalFilter`, `withEntitiesLocalSort`). It loads entities from the backend and then allows for local filtering, sorting, and pagination. ```typescript const productsEntityConfig = entityConfig({ entity: type(), collection: 'product', }); export const ProductsLocalStore = signalStore( { providedIn: 'root' }, withEntities(productsEntityConfig), withCallStatus({ ...productsEntityConfig, initialValue: 'loading' }), // withCallStatus must be before all the other // withEntities* features so the can use the callStatus state withEntitiesLocalPagination({ ...productsEntityConfig, pageSize: 5, }), withEntitiesLocalFilter({ ...productsEntityConfig, defaultFilter: { search: '' }, filterFn: (entity, filter) => !filter?.search || entity?.name.toLowerCase().includes(filter?.search.toLowerCase()), }), withEntitiesLocalSort({ ...productsEntityConfig, defaultSort: { field: 'name', direction: 'asc' }, }), // with withEntitiesLoadingCall, should generally be the after all the other // withEntities* so it can use the state generated by them, // you can see this in the next example for remote filtering, sorting pagination withEntitiesLoadingCall({ ...productsEntityConfig, fetchEntities: () => { return inject(ProductService) .getProducts() .pipe(map((d) => d.resultList)); }, }), ); ``` -------------------------------- ### Use withEntitiesLoadingCall with Local Store Features Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-loading-call.md Example of using withEntitiesLoadingCall with local store features, including pagination, filtering, and sorting. This snippet demonstrates fetching products and mapping the response to the entities list. ```typescript const productsEntityConfig = entityConfig({ entity: type(), collection: 'product', }); export const ProductsLocalStore = signalStore( { providedIn: 'root' }, withEntities(productsEntityConfig), withCallStatus({ ...productsEntityConfig, initialValue: 'loading' }), withEntitiesLocalPagination({ ...productsEntityConfig, pageSize: 5, }), withEntitiesLocalFilter({ ...productsEntityConfig, defaultFilter: { search: '' }, filterFn: (entity, filter) => !filter?.search || entity?.name.toLowerCase().includes(filter?.search.toLowerCase()), }), withEntitiesLocalSort({ ...productsEntityConfig, defaultSort: { field: 'name', direction: 'asc' }, }), withEntitiesLoadingCall({ ...productsEntityConfig, fetchEntities: () => { return inject(ProductService) .getProducts() .pipe(map((d) => d.resultList)); }, }), ); ``` -------------------------------- ### Example: Using with Angular Material / CDK Table Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-local-sort.md Shows how to integrate withAngular Material's MatSort by passing Material table sort events directly to the sortEntities method. ```APIDOC ## Example: Using with Angular Material / CDK Table The `sortEntities` method also accepts `CdkSort` format, allowing you to pass Material table sort events directly: ```typescript // CdkSort format uses 'active' instead of 'field' type CdkSort = { active: keyof Entity | string; direction: 'asc' | 'desc' | ''; }; ``` This enables direct integration with `MatSort`: ```html ...
Name
``` ``` -------------------------------- ### Compose ProductStore with Signal Store Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/utils/extract-store-feature-output.md Shows how to compose multiple NgRx Traits features into a final `ProductStore` using Angular's `signalStore`. This example combines `withProductEntities` and `withProductCalls`. ```typescript export const ProductStore = signalStore( withProductEntities(), withProductCalls(), ); ``` -------------------------------- ### Mix with Other Local Store Features Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-single-selection.md Combine withEntitiesSingleSelection with other traits like pagination, filtering, sorting, and loading for a comprehensive local store. This example demonstrates a product store with multiple features. ```typescript const productsEntityConfig = entityConfig({ entity: type(), collection: 'product', }); export const ProductsLocalStore = signalStore( { providedIn: 'root' }, withEntities(productsEntityConfig), withEntitiesSingleSelection(productsEntityConfig), withCallStatus({ ...productsEntityConfig, initialValue: 'loading' }), withEntitiesLocalPagination({ ...productsEntityConfig, pageSize: 5, }), withEntitiesLocalFilter({ ...productsEntityConfig, defaultFilter: { search: '' }, filterFn: (entity, filter) => !filter?.search || entity?.name.toLowerCase().includes(filter?.search.toLowerCase()), }), withEntitiesLocalSort({ ...productsEntityConfig, defaultSort: { field: 'name', direction: 'asc' }, }), withEntitiesLoadingCall({ ...productsEntityConfig, fetchEntities: () => { return inject(ProductService) .getProducts() .pipe(map((d) => d.resultList)); }, }), ); ``` -------------------------------- ### Test Migration with Sample Project Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/SCHEMATIC_DEVELOPMENT.md Create a temporary test project, copy a sample component with old naming, and then run the migration using `ng update` with the `--migrate-only` and `--verbose` flags. ```bash # Create test project with old naming mkdir /tmp/test-project cd /tmp/test-project ng new myapp --skip-install # Copy sample component with old naming # Run migration ng update @ngrx-traits/signals --migrate-only --verbose ``` -------------------------------- ### Build All Schematics and Migrations Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/SCHEMATIC_DEVELOPMENT.md Build both migrations and schematics using the project's defined targets in nx. ```bash nx run ngrx-traits-signals:build-migrations ``` ```bash nx run ngrx-traits-signals:build-schematics ``` -------------------------------- ### Sync State with Local Storage using Value Mapper Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/api-docs.md This example demonstrates synchronizing store state with local storage using a custom `valueMapper`. This is useful for transforming state before saving or when restoring from storage. It also shows the generated methods for manual storage operations. ```typescript const store = signalStore( withState({ userProfile: { userName: '', email: '', preferences: { theme: 'light', notifications: true }, tempData: null, } }), withSyncToWebStorage({ key: 'user-form', type: 'local', restoreOnInit: true, saveStateChangesAfterMs: 500, // Custom mapper to store only userName and email valueMapper: (store) => ({ stateToStorageValue: () => ({ userName: store.userProfile().userName, email: store.userProfile().email, }), storageValueToState: (savedData) => { patchState(store, { userProfile: { ...store.userProfile(), userName: savedData.userName, email: savedData.email, } }); }, }), }), ); // generates the following methods store.saveToStorage(); store.loadFromStorage(); store.clearFromStore(); ``` -------------------------------- ### Run Unit and Integration Tests Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/SCHEMATICS_README.md Command to run tests, specifically including tests for migrations and schematics. Use '--include' flag to filter test files. ```bash npm test -- --include='**/migrations/**' --include='**/schematics/**' ``` -------------------------------- ### withServerStateTransfer: Sync State with TransferState for SSR (Filter Example) Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/api-docs.md Utilize withServerStateTransfer to synchronize store state with Angular's TransferState API for Server-Side Rendering. This example demonstrates using filterState to selectively transfer specific state properties. ```typescript const store = signalStore( withEntities({ entity, collection }), withCallStatus({ prop: collection, initialValue: 'loading' }), withServerStateTransfer({ key: 'my-state', // optionally, filter the state before transferring filterState: ({ orderItemsEntityMap, orderItemsIds }) => ({ orderItemsEntityMap, orderItemsIds, }), }), ); ``` -------------------------------- ### Run Tests Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/CONTRIBUTING.md Execute the project tests using pnpm. ```bash pnpm test ``` -------------------------------- ### Run All Tests Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/SCHEMATIC_DEVELOPMENT.md Execute all tests, filtering specifically for migration or schematic tests using the --include flag. ```bash npm test -- --include='**/migrations/**' ``` ```bash npm test -- --include='**/schematics/**' ``` -------------------------------- ### Define Rename Pattern Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/SCHEMATICS_README.md Example of defining a regex-based transformation pattern for renaming. The negative lookahead `(?!Entities)` prevents double-migration. ```typescript { pattern: /(\w+)CallStatus(?!Entities)/g, replacement: (match, name) => `${name}EntitiesCallStatus`, description: '{name}CallStatus → {name}EntitiesCallStatus' } ``` -------------------------------- ### Computed signals from mapParams Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-route-params.md This example shows the structure of computed signals generated from the mapParams function, demonstrating type transformation. ```typescript // for withRouteParams(({ id , foo }) => ({ id, foo: +foo })) { id: string; foo: number; } ``` -------------------------------- ### Build the Library Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/CONTRIBUTING.md Build the ngrx-traits library using pnpm. ```bash pnpm build ``` -------------------------------- ### withCallStatus - Using Prop Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-call-status.md Illustrates how to use the `prop` option to customize the names of the generated signals and computed properties. ```APIDOC ## withCallStatus - Using Prop ### Description This example demonstrates how to use the `prop` option within `withCallStatus` to namespace the generated state properties, useful when managing multiple call statuses within a single store. ### Method `signalStore` ### Endpoint N/A (This is a NgRx trait, not a REST endpoint) ### Parameters #### Request Body - **`withCallStatus({ prop: 'user' })`** (function) - Initializes the trait, prefixing generated state with 'user'. - **`prop`** (string) - Required - The prefix for generated state properties. ### Request Example ```typescript import { signalStore, withCallStatus } from '@ngrx-traits/signals'; const store = signalStore(withCallStatus({ prop: 'user' })); ``` ### Response #### Success Response (N/A) This trait modifies the store's state and does not return a direct response. #### State Generated (with `prop: 'user'`) - `userCallStatus`: `'init' | 'loading' | 'loaded' | { error: unknown }` - `isUserLoading`: `Signal` - `isUserLoaded`: `Signal` - `userError`: `Signal` ``` -------------------------------- ### Run Tests and Build After Migration Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/MIGRATION_GUIDE.md Commands to run after migrating to ensure the project functions correctly. This includes executing the test suite and building the project. ```bash npm test npm run build ``` -------------------------------- ### Enable Verbose Logging for Debugging Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/SCHEMATIC_DEVELOPMENT.md Uncomment debug logging lines in `rename-entities-suffix.ts` to get more detailed information during migration runs. ```typescript context.logger.debug(`Processing ${filePath}`); context.logger.debug(`Found matches: ${JSON.stringify(matches)}`); ``` -------------------------------- ### Verify Migration Build and Tests Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/SCHEMATIC_DEVELOPMENT.md After adding new patterns, run specific tests for `pattern-matchers.spec.ts` and rebuild migrations to ensure everything is working correctly. ```bash npm test -- --include='**/pattern-matchers.spec.ts' ``` ```bash tsc -p tools/tsconfig.migration.json ``` -------------------------------- ### Define Effects for a Trait Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/common/src/lib/custom-traits.md Create NgRx effects for a trait using `createEffect`. This example shows an effect that listens for the `loadProduct` action. ```typescript effects: ({ allActions, allSelectors, allConfigs }) => { @Injectable() class LoadProductEffect extends TraitEffect { loadProduct$ = createEffect(() => { return this.actions$.pipe( ofType(allActions.loadProduct), ``` -------------------------------- ### After Renaming Collection Properties Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/utils/rename-collection.md Example of store properties after a collection rename operation. This demonstrates the transformed properties, such as 'productCallStatus' becoming 'productEntitiesCallStatus'. ```typescript // After store.productEntitiesCallStatus(); store.isProductEntitiesLoading(); store.loadProductEntitiesPage({ pageIndex: 1 }); store.productEntitiesFilter(); ``` -------------------------------- ### Sync Entities Filter, Pagination, Sort, and Single Selection to Route Query Params Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-sync-to-route-query-params.md This example demonstrates syncing entities filter, pagination, sort, and single selection to route query params. It requires withEntities, withCallStatus, withEntitiesRemoteFilter, withEntitiesRemotePagination, withEntitiesRemoteSort, and withEntitiesLoadingCall traits. ```typescript export const ProductsRemoteStore = signalStore( { providedIn: 'root' }, // requires at least withEntities and withCallStatus withEntities({ entity, collection }), withCallStatus({ prop: collection, initialValue: 'loading' }), withEntitiesRemoteFilter({ entity, collection, }), withEntitiesRemotePagination({ entity, collection, }), withEntitiesRemoteSort({ entity, collection, defaultSort: { field: 'name', direction: 'asc' }, }), withEntitiesLoadingCall({ collection, fetchEntities: ({ productEntitiesFilter, productEntitiesPagedRequest, productEntitiesSort }) => { return inject(ProductService) .getProducts({ search: productEntitiesFilter().name, take: productEntitiesPagedRequest().size, skip: productEntitiesPagedRequest().startIndex, sortColumn: productEntitiesSort().field, sortAscending: productEntitiesSort().direction === 'asc', }) .pipe( map((d) => ({ entities: d.resultList, total: d.total, })), ); }, }), // syncs the entities filter, pagination, sort and single selection to the route query params withEntitiesSyncToRouteQueryParams({ entity, collection, }) ); ``` -------------------------------- ### Reducer with Typed Actions Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/common/src/lib/custom-traits.md Example of a reducer using typed actions, ensuring correct type checking when accessing actions from different traits. ```typescript reducer: ({ initialState, allActions: actions }) => { const allActions = actions as LoadProductActions & LoadEntitiesActions; createReducer( initialState, on(allActions.loadEntitiesSuccess, (state, { product }) => ({ // ... })) ``` -------------------------------- ### Paginate Product Entities with Caching Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-remote-pagination.md Sets up state management for paginating product entities, including caching previous pages and handling loading states. Requires ProductService for fetching. ```typescript const entityConfig = entityConfig({ entity: type(), collection: 'product' }); export const store = signalStore( withEntities(entityConfig), withCallStatus({ ...entityConfig, initialValue: 'loading' }), withEntitiesRemotePagination({ ...entityConfig, pageSize: 5, pagesToCache: 2, }), withEntitiesLoadingCall({ ...entityConfig, fetchEntities: ({ productEntitiesPagedRequest }) => { return inject(ProductService) .getProducts({ take: productEntitiesPagedRequest().size, skip: productEntitiesPagedRequest().startIndex, }) .pipe( map((d) => ({ entities: d.resultList, total: d.total, })), ); }, }), ); ``` ```html @for (user of store.productEntitiesCurrentPage.entities(); track user.id){ {{ user.name }} } ``` -------------------------------- ### Store Definition Before Migration Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/signals/MIGRATION_GUIDE.md Example of a store definition using @ngrx-traits/signals before the v20.0.0 migration. It includes entities, call status, and pagination traits. ```typescript // Store definition export const createProductsStore = () => signalStore( withEntities({ entity: Product }), withCallStatus({ initialState: LoadingState.Init }), withPagination() ); // Component usage export class ProductsComponent { store = inject(createProductsStore); loadData() { // Properties const filter = this.store.productsFilter(); const page = this.store.productsCurrentPage(); const loading = this.store.isProductsLoading(); const error = this.store.productsError(); // Methods this.store.setProductsLoading(true); this.store.loadProductsPage({ page: 1 }); this.store.resetProductsFilter(); this.store.sortProducts({ field: 'name' }); } } ``` -------------------------------- ### Collection-Specific Computed Signal Example Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-hybrid-filter.md Illustrates the 'isUserFilterChanged' computed signal, generated when a collection name is provided, indicating filter changes for that collection. ```typescript isUserFilterChanged: Signal; ``` -------------------------------- ### Triggering Initial Calls with withCall Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-calls.md Use the `withCall: true` option to execute a call immediately when the store initializes. This is a concise alternative to using `withHooks` for initial data loading. For calls with parameters, provide an object to `callWith`. ```typescript const productsEntityConfig = entityConfig({ entity: type(), collection: 'product', }); const store = signalStore( withEntities(productsEntityConfig), withCalls(({ productsSelectedEntity }) => ({ loadProductDetail: callConfig({ call: ({ id }: { id: string }) => inject(ProductService).getProductDetail(id), resultProp: 'productDetail', callWith: {id: 1}, // this will be call on init with that param }), loadProducts: callConfig({ call: () => inject(ProductService).getProducts(), storeResult: false, onSuccess: (res) => { patchState( store, setAllEntities(res.resultList, productsEntityConfig), ); }, // for calls with no params passing true will execute the call withCall: true }), })), ); ``` -------------------------------- ### Default Entities Paged Request Signal Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-entities-remote-pagination.md This computed signal represents the parameters for a paged request, including the starting index, size, and page number. ```typescript entitiesPagedRequest: Signal<{ startIndex: number; size: number; page: number }>; ``` -------------------------------- ### Custom Transformation with valueMapper Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/traits/with-server-state-transfer.md The `valueMapper` option allows for custom serialization and deserialization logic when transferring state. This example transfers only `userName` and `email` from `userProfile`. ```typescript const store = signalStore( withState({ userProfile: { userName: '', email: '', preferences: { theme: 'light', notifications: true }, tempData: null, } }), withServerStateTransfer({ key: 'user-profile', // Only transfer userName and email valueMapper: (store) => ({ stateToTransferValue: () => ({ userName: store.userProfile().userName, email: store.userProfile().email, }), transferValueToState: (savedData) => { patchState(store, { userProfile: { ...store.userProfile(), userName: savedData.userName, email: savedData.email, } }); }, }), }), ); ``` -------------------------------- ### Invalidate Cache Entries Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/core/src/lib/cache/README.md Invalidate cache entries using a specific key. Invalidating with a key like ['stores'] will also invalidate all entries that start with that key. ```typescript this.store.dispatch(CacheActions.invalidateCache({key:['stores']})); ``` -------------------------------- ### Create Signal Store with Custom Entity Config Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/apps/docs/src/app/pages/docs/getting-started/start-coding.md Initialize a signal store using various withEntities traits, spreading the previously defined custom entity configuration. Ensure all traits that interact with entities use this configuration. ```typescript export const ProductsLocalStore = signalStore( withEntities(entityConfig), withCallStatus({ ...entityConfig, initialValue: "loading" }), withEntitiesLocalPagination({ ...entityConfig, pageSize: 5 }), withEntitiesLoadingCall({ ...entityConfig, fetchEntities: () => inject(ProductService) .getProducts() .pipe((res) => res.resultList), }), withCalls(() => ({ loadProductDetail: ({ id }: { id: string }) => inject(ProductService).getProductDetail(id), })), ); ``` -------------------------------- ### Create Local Traits Service with Effects Source: https://github.com/gabrielguerrero/ngrx-traits/blob/main/libs/ngrx-traits/core/api-docs.md Example of creating a local traits service that includes effects. Ensure `this.traits.addEffects(this)` is called in the constructor if effects are present. ```typescript const productFeatureFactory = createEntityFeatureFactory( { entityName: 'product' }, addLoadEntitiesTrait(), addSelectEntityTrait() ); Injectable() export class ProductsLocalTraits extends TraitsLocalStore< typeof productFeatureFactory > { loadProducts$ = createEffect(() => this.actions$.pipe( ofType(this.localActions.loadProducts), switchMap(() => //call your service to get the products data this.productService.getProducts().pipe( map((res) => this.localActions.loadProductsSuccess({ entities: res.resultList }) ), catchError(() => of(this.localActions.loadProductsFail())) ) ) ) ); constructor(injector: Injector, private productService: ProductService) { super(injector); this.traits.addEffects(this); // IMPORTANT! add this line if the service has effects } setup(): LocalTraitsConfig { return { componentName: 'ProductsPickerComponent', traitsFactory: productFeatureFactory, }; } } // use in component later Component({ selector: 'some-component', template: `
some content
`, providers: [ProductsLocalTraits], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProductSelectDialogComponent implements OnInit { constructor(private store: Store, private traits: ProductsLocalTraits) {} ngOnInit() { this.store.dispatch(this.traits.localActions.loadProducts()); } } ```