diff --git a/libs/ngrx-traits/signals/src/lib/with-entities-filter/with-entities-filter.util.ts b/libs/ngrx-traits/signals/src/lib/with-entities-filter/with-entities-filter.util.ts index 37487ab..512d0ea 100644 --- a/libs/ngrx-traits/signals/src/lib/with-entities-filter/with-entities-filter.util.ts +++ b/libs/ngrx-traits/signals/src/lib/with-entities-filter/with-entities-filter.util.ts @@ -112,13 +112,15 @@ export function getQueryMapperForEntitiesFilter(config?: { const filter = store[filterKey] as Signal< EntitiesFilterState['entitiesFilter'] >; - return computed(() => - config?.filterMapper - ? (config?.filterMapper.filterToQueryParams(filter()) as any) - : { - filter: JSON.stringify({ ...filter() }), - }, - ); + return filter + ? computed(() => + config?.filterMapper + ? (config?.filterMapper.filterToQueryParams(filter()) as any) + : { + filter: JSON.stringify({ ...filter() }), + }, + ) + : null; }, }; } diff --git a/libs/ngrx-traits/signals/src/lib/with-entities-pagination/with-entities-local-pagination.util.ts b/libs/ngrx-traits/signals/src/lib/with-entities-pagination/with-entities-local-pagination.util.ts index 05842d4..0d6194a 100644 --- a/libs/ngrx-traits/signals/src/lib/with-entities-pagination/with-entities-local-pagination.util.ts +++ b/libs/ngrx-traits/signals/src/lib/with-entities-pagination/with-entities-local-pagination.util.ts @@ -83,9 +83,11 @@ export function getQueryMapperForEntitiesPagination(config?: { const pagination = store[paginationKey] as Signal< EntitiesPaginationLocalState['entitiesPagination'] >; - return computed(() => ({ - page: (pagination().currentPage + 1).toString(), - })); + return pagination + ? computed(() => ({ + page: (pagination().currentPage + 1).toString(), + })) + : null; }, }; } diff --git a/libs/ngrx-traits/signals/src/lib/with-entities-sort/with-entities-local-sort.util.ts b/libs/ngrx-traits/signals/src/lib/with-entities-sort/with-entities-local-sort.util.ts index 58bc935..993dfbf 100644 --- a/libs/ngrx-traits/signals/src/lib/with-entities-sort/with-entities-local-sort.util.ts +++ b/libs/ngrx-traits/signals/src/lib/with-entities-sort/with-entities-local-sort.util.ts @@ -116,10 +116,12 @@ export function getQueryMapperForEntitiesSort(config?: { const sort = store[sortKey] as Signal< EntitiesSortState['entitiesSort'] >; - return computed(() => ({ - sortBy: sort().field as string, - sortDirection: sort().direction, - })); + return sort + ? computed(() => ({ + sortBy: sort().field as string, + sortDirection: sort().direction, + })) + : null; }, }; } diff --git a/libs/ngrx-traits/signals/src/lib/with-sync-to-route-query-params/with-entities-sync-to-route-query-params.spec.ts b/libs/ngrx-traits/signals/src/lib/with-sync-to-route-query-params/with-entities-sync-to-route-query-params.spec.ts index 921f84c..74f10b6 100644 --- a/libs/ngrx-traits/signals/src/lib/with-sync-to-route-query-params/with-entities-sync-to-route-query-params.spec.ts +++ b/libs/ngrx-traits/signals/src/lib/with-sync-to-route-query-params/with-entities-sync-to-route-query-params.spec.ts @@ -262,6 +262,47 @@ describe('withEntitiesSyncToRouteQueryParams', () => { }); })); + it('filter store and url when there is no sort or pagination', fakeAsync(() => { + const Store = signalStore( + withEntities({ entity }), + withCallStatus({ initialValue: 'loading' }), + withEntitiesLocalFilter({ + entity, + defaultFilter: { search: '', foo: 'bar' }, + filterFn: (entity, filter) => + !filter?.search || + entity?.name.toLowerCase().includes(filter?.search.toLowerCase()), + }), + withEntitiesLoadingCall({ + fetchEntities: ({}) => { + let result = [...mockProducts.slice(0, 40)]; + const total = result.length; + const response = { entities: result, total }; + return of(response); + }, + }), + withEntitiesSyncToRouteQueryParams({ entity }), + ); + const { store, router } = init({ + Store, + queryParams: { filter: JSON.stringify({ search: 'foo', foo: 'bar' }) }, + }); + expect(store.entitiesFilter()).toEqual({ search: 'foo', foo: 'bar' }); + + store.filterEntities({ + filter: { search: 'foo3', foo: 'bar4' }, + forceLoad: true, + }); + tick(400); + expect(router.navigate).toBeCalledWith([], { + relativeTo: expect.anything(), + queryParams: expect.objectContaining({ + filter: JSON.stringify({ search: 'foo3', foo: 'bar4' }), + }), + queryParamsHandling: 'merge', + }); + })); + it('filter url query params should update store, with custom filterMapper', () => { const Store = signalStore( localStoreFeature(), @@ -358,6 +399,46 @@ describe('withEntitiesSyncToRouteQueryParams', () => { queryParamsHandling: 'merge', }); })); + + it('changes on entities sort should store and url if no filter or pagination present', fakeAsync(() => { + const Store = signalStore( + withEntities({ entity }), + withCallStatus({ initialValue: 'loading' }), + withEntitiesLocalSort({ + entity, + defaultSort: { field: 'name', direction: 'asc' }, + }), + withEntitiesLoadingCall({ + fetchEntities: ({}) => { + let result = [...mockProducts.slice(0, 40)]; + const total = result.length; + const response = { entities: result, total }; + return of(response); + }, + }), + withEntitiesSyncToRouteQueryParams({ entity }), + ); + const { store, router } = init({ + Store, + queryParams: { sortBy: 'description', sortDirection: 'desc' }, + }); + expect(store.entitiesSort()).toEqual({ + field: 'description', + direction: 'desc', + }); + store.sortEntities({ + sort: { field: 'name', direction: 'asc' }, + }); + tick(400); + expect(router.navigate).toBeCalledWith([], { + relativeTo: expect.anything(), + queryParams: expect.objectContaining({ + sortBy: 'name', + sortDirection: 'asc', + }), + queryParamsHandling: 'merge', + }); + })); }); describe('entities single selection', () => { @@ -474,6 +555,47 @@ describe('withEntitiesSyncToRouteQueryParams', () => { queryParamsHandling: 'merge', }); })); + + it('changes on entities page should sync to store and url when there is no filter or sort', fakeAsync(() => { + const load = new Subject(); + const Store = signalStore( + withEntities({ entity }), + withCallStatus({ initialValue: 'loading' }), + withEntitiesLocalPagination({ entity, pageSize: 10 }), + withEntitiesLoadingCall({ + fetchEntities: ({}) => { + let result = [...mockProducts.slice(0, 40)]; + const total = result.length; + const response = { entities: result, total }; + return load + ? load.pipe( + filter(Boolean), + map(() => response), + ) + : of(response); + }, + }), + withEntitiesSyncToRouteQueryParams({ entity }), + ); + const { store, router } = init({ + Store, + queryParams: { page: '2' }, + }); + TestBed.flushEffects(); + load.next(true); + tick(400); + expect(store.entitiesPagination().currentPage).toEqual(1); + + store.loadEntitiesPage({ pageIndex: 2 }); + tick(400); + expect(router.navigate).toBeCalledWith([], { + relativeTo: expect.anything(), + queryParams: expect.objectContaining({ + page: '3', + }), + queryParamsHandling: 'merge', + }); + })); }); it('multiple url and state changes should sync correctly', fakeAsync(() => {