From 5a0edbe33dfd40cd9ae305b88a4092e838293b42 Mon Sep 17 00:00:00 2001
From: Gabriel Guerrero
Date: Wed, 17 Apr 2024 14:37:09 +0100
Subject: [PATCH 1/9] refactor: refactor example using ProductStore to Branch
to not confuse with ngrx store
---
.../cache-and-dropdowns-page.component.ts | 21 ++---
.../branch-dropdown.component.ts} | 81 +++++++++---------
.../store.local-traits.ts | 45 +++++-----
.../department-dropdown.component.ts | 80 ++++++++---------
.../department.local-traits.ts | 35 ++++----
.../src/app/examples/models/index.ts | 14 +--
...cts-store.service.ts => branch.service.ts} | 27 +++---
...-stores.handler.ts => branches.handler.ts} | 85 +++++++++----------
.../examples/services/mock-backend/index.ts | 5 +-
.../products-branch-dropdown.component.ts | 6 +-
.../products-branch.store.ts | 8 +-
libs/ngrx-traits/core/api-docs.md | 50 +++++++----
12 files changed, 236 insertions(+), 221 deletions(-)
rename apps/example-app/src/app/examples/cache-and-dropdowns-page/components/{store-dropdown/store-dropdown.component.ts => branch-dropdown/branch-dropdown.component.ts} (70%)
rename apps/example-app/src/app/examples/cache-and-dropdowns-page/components/{store-dropdown => branch-dropdown}/store.local-traits.ts (65%)
rename apps/example-app/src/app/examples/services/{products-store.service.ts => branch.service.ts} (56%)
rename apps/example-app/src/app/examples/services/mock-backend/{product-stores.handler.ts => branches.handler.ts} (55%)
diff --git a/apps/example-app/src/app/examples/cache-and-dropdowns-page/cache-and-dropdowns-page.component.ts b/apps/example-app/src/app/examples/cache-and-dropdowns-page/cache-and-dropdowns-page.component.ts
index 33146c28..96d17ac2 100644
--- a/apps/example-app/src/app/examples/cache-and-dropdowns-page/cache-and-dropdowns-page.component.ts
+++ b/apps/example-app/src/app/examples/cache-and-dropdowns-page/cache-and-dropdowns-page.component.ts
@@ -1,28 +1,29 @@
-import { Component, ChangeDetectionStrategy } from '@angular/core';
+import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
+ ReactiveFormsModule,
UntypedFormControl,
UntypedFormGroup,
- ReactiveFormsModule,
} from '@angular/forms';
-import { DepartmentDropdownComponent } from './components/department-dropdown/department-dropdown.component';
-import { StoreDropdownComponent } from './components/store-dropdown/store-dropdown.component';
import { MatCardModule } from '@angular/material/card';
+import { BranchDropdownComponent } from './components/branch-dropdown/branch-dropdown.component';
+import { DepartmentDropdownComponent } from './components/department-dropdown/department-dropdown.component';
+
@Component({
selector: 'ngrx-traits-cache-and-dropdowns-page',
template: `
@@ -35,13 +36,13 @@ import { MatCardModule } from '@angular/material/card';
imports: [
MatCardModule,
ReactiveFormsModule,
- StoreDropdownComponent,
+ BranchDropdownComponent,
DepartmentDropdownComponent,
],
})
export class CacheAndDropdownsPageComponent {
form = new UntypedFormGroup({
- store: new UntypedFormControl(),
+ branch: new UntypedFormControl(),
department: new UntypedFormControl(),
});
}
diff --git a/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/store-dropdown/store-dropdown.component.ts b/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/branch-dropdown/branch-dropdown.component.ts
similarity index 70%
rename from apps/example-app/src/app/examples/cache-and-dropdowns-page/components/store-dropdown/store-dropdown.component.ts
rename to apps/example-app/src/app/examples/cache-and-dropdowns-page/components/branch-dropdown/branch-dropdown.component.ts
index e993f507..e5e6dbd2 100644
--- a/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/store-dropdown/store-dropdown.component.ts
+++ b/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/branch-dropdown/branch-dropdown.component.ts
@@ -1,51 +1,46 @@
+import { AsyncPipe } from '@angular/common';
import {
- Component,
- OnInit,
ChangeDetectionStrategy,
+ Component,
Input,
- Output,
OnDestroy,
+ OnInit,
+ Output,
} from '@angular/core';
-import { ProductsStore } from '../../../models';
-import { ProductsStoreLocalTraits } from './store.local-traits';
-import { createSelector, Store } from '@ngrx/store';
+import { input } from '@angular/core';
import {
ControlValueAccessor,
- UntypedFormControl,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
+ UntypedFormControl,
} from '@angular/forms';
+import { MatOptionModule } from '@angular/material/core';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatSelectModule } from '@angular/material/select';
+import { createSelector, Store } from '@ngrx/store';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
-import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
-import { MatOptionModule } from '@angular/material/core';
+
import { SearchOptionsComponent } from '../../../components/search-options/search-options.component';
-import { MatSelectModule } from '@angular/material/select';
-import { MatFormFieldModule } from '@angular/material/form-field';
-import { AsyncPipe } from '@angular/common';
-import { input } from '@angular/core';
+import { Branch } from '../../../models';
+import { BranchLocalTraits } from './store.local-traits';
@Component({
- selector: 'store-dropdown',
+ selector: 'branch-dropdown',
template: `
@if (data$ | async; as data) {
-
- Store
+
+ Branch
+ >
@for (item of data.stores; track item) {
-
+
{{ item.name }}
}
@@ -57,7 +52,7 @@ import { input } from '@angular/core';
}
- `,
+ `,
styles: [
`
:host {
@@ -70,10 +65,10 @@ import { input } from '@angular/core';
],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
- ProductsStoreLocalTraits,
+ BranchLocalTraits,
{
provide: NG_VALUE_ACCESSOR,
- useExisting: StoreDropdownComponent,
+ useExisting: BranchDropdownComponent,
multi: true,
},
],
@@ -85,36 +80,38 @@ import { input } from '@angular/core';
SearchOptionsComponent,
MatOptionModule,
MatProgressSpinnerModule,
- AsyncPipe
-],
+ AsyncPipe,
+ ],
})
-export class StoreDropdownComponent
+export class BranchDropdownComponent
implements OnInit, ControlValueAccessor, OnDestroy
{
control = new UntypedFormControl();
data$ = this.store.select(
createSelector(
- this.traits.localSelectors.isStoresLoading,
- this.traits.localSelectors.selectStoresList,
- (isLoading, stores) => ({ isLoading, stores })
- )
+ this.traits.localSelectors.isBranchesLoading,
+ this.traits.localSelectors.selectBranchesList,
+ (isLoading, stores) => ({ isLoading, stores }),
+ ),
);
private onTouch: any;
destroy = new Subject();
- @Input() set value(value: ProductsStore) {
+ @Input() set value(value: Branch) {
this.control.setValue(value);
}
- @Output() valueChanges = this.control
- .valueChanges as Observable;
+ @Output() valueChanges = this.control.valueChanges as Observable;
- constructor(private store: Store, private traits: ProductsStoreLocalTraits) {}
+ constructor(
+ private store: Store,
+ private traits: BranchLocalTraits,
+ ) {}
ngOnInit(): void {
- this.store.dispatch(this.traits.localActions.loadStores());
+ this.store.dispatch(this.traits.localActions.loadBranches());
}
- writeValue(value: ProductsStore): void {
+ writeValue(value: Branch): void {
this.control.setValue(value);
}
@@ -132,12 +129,12 @@ export class StoreDropdownComponent
this.destroy.next();
this.destroy.complete();
}
- compareById(value: ProductsStore, option: ProductsStore) {
+ compareById(value: Branch, option: Branch) {
return value && option && value.id == option.id;
}
search(text: string | undefined) {
this.store.dispatch(
- this.traits.localActions.filterStores({ filters: { search: text } })
+ this.traits.localActions.filterBranches({ filters: { search: text } }),
);
}
}
diff --git a/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/store-dropdown/store.local-traits.ts b/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/branch-dropdown/store.local-traits.ts
similarity index 65%
rename from apps/example-app/src/app/examples/cache-and-dropdowns-page/components/store-dropdown/store.local-traits.ts
rename to apps/example-app/src/app/examples/cache-and-dropdowns-page/components/branch-dropdown/store.local-traits.ts
index 7bcf6148..9f2d5f5f 100644
--- a/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/store-dropdown/store.local-traits.ts
+++ b/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/branch-dropdown/store.local-traits.ts
@@ -1,19 +1,20 @@
+import { Injectable } from '@angular/core';
+import {
+ addFilterEntitiesTrait,
+ addLoadEntitiesTrait,
+} from '@ngrx-traits/common';
import {
cache,
createEntityFeatureFactory,
LocalTraitsConfig,
TraitsLocalStore,
} from '@ngrx-traits/core';
-import {
- addFilterEntitiesTrait,
- addLoadEntitiesTrait,
-} from '@ngrx-traits/common';
-import { ProductsStore, ProductsStoreFilter } from '../../../models';
-import { Injectable } from '@angular/core';
-import { ProductsStoreService } from '../../../services/products-store.service';
-import { catchError, exhaustMap, map } from 'rxjs/operators';
import { createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
+import { catchError, exhaustMap, map } from 'rxjs/operators';
+
+import { Branch, BranchFilter } from '../../../models';
+import { BranchService } from '../../../services/branch.service';
export const storeCacheKeys = {
all: ['stores'],
@@ -22,9 +23,9 @@ export const storeCacheKeys = {
};
const storeFeatureFactory = createEntityFeatureFactory(
- { entityName: 'store' },
- addLoadEntitiesTrait(),
- addFilterEntitiesTrait({
+ { entityName: 'branch', entitiesName: 'branches' },
+ addLoadEntitiesTrait(),
+ addFilterEntitiesTrait({
filterFn: (filter, entity) => {
const searchString = filter?.search?.toLowerCase?.();
return (
@@ -33,32 +34,34 @@ const storeFeatureFactory = createEntityFeatureFactory(
entity.address.toLowerCase().includes(searchString)
);
},
- })
+ }),
);
@Injectable()
-export class ProductsStoreLocalTraits extends TraitsLocalStore<
+export class BranchLocalTraits extends TraitsLocalStore<
typeof storeFeatureFactory
> {
- constructor(private storeService: ProductsStoreService) {
+ constructor(private storeService: BranchService) {
super();
this.traits.addEffects(this);
}
- loadStores$ = createEffect(() => {
+ loadBranches$ = createEffect(() => {
return this.actions$.pipe(
- ofType(this.localActions.loadStores),
+ ofType(this.localActions.loadBranches),
exhaustMap(() =>
cache({
key: storeCacheKeys.list(),
store: this.store,
- source: this.storeService.getStores(),
+ source: this.storeService.getBranches(),
// no expire param so is stored forever
}).pipe(
- map((res) => this.localActions.loadStoresSuccess({ entities: res })),
- catchError(() => of(this.localActions.loadStoresFail()))
- )
- )
+ map((res) =>
+ this.localActions.loadBranchesSuccess({ entities: res.resultList }),
+ ),
+ catchError(() => of(this.localActions.loadBranchesFail())),
+ ),
+ ),
);
});
diff --git a/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/department-dropdown/department-dropdown.component.ts b/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/department-dropdown/department-dropdown.component.ts
index c0d6dbca..db7e1e6a 100644
--- a/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/department-dropdown/department-dropdown.component.ts
+++ b/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/department-dropdown/department-dropdown.component.ts
@@ -1,3 +1,4 @@
+import { AsyncPipe } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
@@ -5,49 +6,50 @@ import {
OnDestroy,
Output,
} from '@angular/core';
+import { input } from '@angular/core';
import {
ControlValueAccessor,
- UntypedFormControl,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
+ UntypedFormControl,
} from '@angular/forms';
+import { MatOptionModule } from '@angular/material/core';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatSelectModule } from '@angular/material/select';
import { createSelector, Store } from '@ngrx/store';
-import { ProductsStore } from '../../../models';
import { Observable, Subject } from 'rxjs';
-import { DepartmentLocalTraits } from './department.local-traits';
import { takeUntil } from 'rxjs/operators';
-import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
-import { MatOptionModule } from '@angular/material/core';
+
import { SearchOptionsComponent } from '../../../components/search-options/search-options.component';
-import { MatSelectModule } from '@angular/material/select';
-import { MatFormFieldModule } from '@angular/material/form-field';
-import { AsyncPipe } from '@angular/common';
-import { input } from '@angular/core';
+import { Branch } from '../../../models';
+import { DepartmentLocalTraits } from './department.local-traits';
@Component({
selector: 'department-dropdown',
template: `
@if (data$ | async; as data) {
-
- Department
-
-
- @for (item of data.stores; track item) {
-
- {{ item.name }}
-
- } @if (data.isLoading) {
-
-
-
- }
-
-
+
+ Department
+
+
+ @for (item of data.stores; track item) {
+
+ {{ item.name }}
+
+ }
+ @if (data.isLoading) {
+
+
+
+ }
+
+
}
`,
styles: [
@@ -92,13 +94,13 @@ export class DepartmentDropdownComponent
createSelector({
isLoading: this.traits.localSelectors.isDepartmentsLoading,
stores: this.traits.localSelectors.selectDepartmentsList,
- })
+ }),
);
private onTouch: any;
destroy = new Subject();
- @Input() set value(value: ProductsStore) {
+ @Input() set value(value: Branch) {
this.writeValue(value);
}
@Input() set storeId(storeId: number | undefined) {
@@ -107,16 +109,18 @@ export class DepartmentDropdownComponent
this.store.dispatch(
this.traits.localActions.filterDepartments({
filters: { storeId },
- })
+ }),
);
}
- @Output() valueChanges = this.control
- .valueChanges as Observable;
+ @Output() valueChanges = this.control.valueChanges as Observable;
- constructor(private store: Store, private traits: DepartmentLocalTraits) {}
+ constructor(
+ private store: Store,
+ private traits: DepartmentLocalTraits,
+ ) {}
- writeValue(value: ProductsStore): void {
+ writeValue(value: Branch): void {
this.control.setValue(value);
}
@@ -135,7 +139,7 @@ export class DepartmentDropdownComponent
this.destroy.complete();
}
- compareById(value: ProductsStore, option: ProductsStore) {
+ compareById(value: Branch, option: Branch) {
return value && option && value.id == option.id;
}
@@ -144,7 +148,7 @@ export class DepartmentDropdownComponent
this.traits.localActions.filterDepartments({
filters: { search: text },
patch: true,
- })
+ }),
);
}
}
diff --git a/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/department-dropdown/department.local-traits.ts b/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/department-dropdown/department.local-traits.ts
index a8dbfe45..a4e28c3b 100644
--- a/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/department-dropdown/department.local-traits.ts
+++ b/apps/example-app/src/app/examples/cache-and-dropdowns-page/components/department-dropdown/department.local-traits.ts
@@ -1,21 +1,22 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
+import { Injectable } from '@angular/core';
+import {
+ addFilterEntitiesTrait,
+ addLoadEntitiesTrait,
+} from '@ngrx-traits/common';
import {
cache,
createEntityFeatureFactory,
LocalTraitsConfig,
TraitsLocalStore,
} from '@ngrx-traits/core';
-import {
- addFilterEntitiesTrait,
- addLoadEntitiesTrait,
-} from '@ngrx-traits/common';
-import { Department, DepartmentFilter } from '../../../models';
-import { Injectable } from '@angular/core';
-import { ProductsStoreService } from '../../../services/products-store.service';
-import { catchError, exhaustMap, map } from 'rxjs/operators';
import { concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
-import { storeCacheKeys } from '../store-dropdown/store.local-traits';
+import { catchError, exhaustMap, map } from 'rxjs/operators';
+
+import { Department, DepartmentFilter } from '../../../models';
+import { BranchService } from '../../../services/branch.service';
+import { storeCacheKeys } from '../branch-dropdown/store.local-traits';
const departmentFeatureFactory = createEntityFeatureFactory(
{ entityName: 'department' },
@@ -29,14 +30,14 @@ const departmentFeatureFactory = createEntityFeatureFactory(
},
isRemoteFilter: (previous, current) =>
previous?.storeId !== current?.storeId,
- })
+ }),
);
@Injectable()
export class DepartmentLocalTraits extends TraitsLocalStore<
typeof departmentFeatureFactory
> {
- constructor(private storeService: ProductsStoreService) {
+ constructor(private storeService: BranchService) {
super();
this.traits.addEffects(this);
}
@@ -45,24 +46,24 @@ export class DepartmentLocalTraits extends TraitsLocalStore<
return this.actions$.pipe(
ofType(this.localActions.loadDepartments),
concatLatestFrom(() =>
- this.store.select(this.localSelectors.selectDepartmentsFilter)
+ this.store.select(this.localSelectors.selectDepartmentsFilter),
),
exhaustMap(([, filters]) =>
cache({
key: storeCacheKeys.departments(filters!.storeId),
store: this.store,
- source: this.storeService.getStoreDepartments(filters!.storeId),
+ source: this.storeService.getBranchDepartments(filters!.storeId),
expires: 1000 * 60 * 3,
maxCacheSize: 3,
}).pipe(
map((res) =>
this.localActions.loadDepartmentsSuccess({
entities: res,
- })
+ }),
),
- catchError(() => of(this.localActions.loadDepartmentsFail()))
- )
- )
+ catchError(() => of(this.localActions.loadDepartmentsFail())),
+ ),
+ ),
);
});
diff --git a/apps/example-app/src/app/examples/models/index.ts b/apps/example-app/src/app/examples/models/index.ts
index f33b886a..f3760c33 100644
--- a/apps/example-app/src/app/examples/models/index.ts
+++ b/apps/example-app/src/app/examples/models/index.ts
@@ -17,13 +17,13 @@ export interface ProductDetail extends Product {
image: string;
}
-export interface ProductsStore {
+export interface Branch {
id: number;
name: string;
address: string;
}
-export interface ProductsStoreDetail {
+export interface BranchDetail {
id: number;
name: string;
phone: string;
@@ -36,19 +36,19 @@ export interface ProductsStoreDetail {
manager: string;
departments: Department[];
}
-export type ProductsStoreQuery = {
+export type BranchQuery = {
search?: string | undefined;
- sortColumn?: keyof ProductsStore | undefined;
+ sortColumn?: keyof Branch | undefined;
sortAscending?: string | undefined;
skip?: string | undefined;
take?: string | undefined;
};
-export interface ProductsStoreResponse {
- resultList: ProductsStore[];
+export interface BranchResponse {
+ resultList: Branch[];
total: number;
}
-export interface ProductsStoreFilter {
+export interface BranchFilter {
search?: string;
}
diff --git a/apps/example-app/src/app/examples/services/products-store.service.ts b/apps/example-app/src/app/examples/services/branch.service.ts
similarity index 56%
rename from apps/example-app/src/app/examples/services/products-store.service.ts
rename to apps/example-app/src/app/examples/services/branch.service.ts
index f6119e35..19e2e671 100644
--- a/apps/example-app/src/app/examples/services/products-store.service.ts
+++ b/apps/example-app/src/app/examples/services/branch.service.ts
@@ -1,39 +1,36 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
import { delay, map } from 'rxjs/operators';
-import {
- Product,
- ProductsStore,
- ProductsStoreDetail,
- ProductsStoreResponse,
-} from '../models';
+import { Branch, BranchDetail, BranchResponse, Product } from '../models';
@Injectable({ providedIn: 'root' })
-export class ProductsStoreService {
+export class BranchService {
constructor(private httpClient: HttpClient) {}
- getStores(options?: {
+ getBranches(options?: {
search?: string | undefined;
sortColumn?: keyof Product | string | undefined;
sortAscending?: boolean | undefined;
skip?: number | undefined;
take?: number | undefined;
- }) {
- console.log('getStores', options);
+ }): Observable {
return this.httpClient
- .get('/stores', {
+ .get('/branches', {
params: { ...options, search: options?.search ?? '' },
})
.pipe(delay(500));
}
- getStoreDetails(id: number) {
+ getBranchDetails(id: number) {
return this.httpClient
- .get('/stores/' + id)
+ .get('/branches/' + id)
.pipe(delay(500));
}
- getStoreDepartments(storeId: number) {
- return this.getStoreDetails(storeId).pipe(map((v) => v?.departments || []));
+ getBranchDepartments(storeId: number) {
+ return this.getBranchDetails(storeId).pipe(
+ map((v) => v?.departments || []),
+ );
}
}
diff --git a/apps/example-app/src/app/examples/services/mock-backend/product-stores.handler.ts b/apps/example-app/src/app/examples/services/mock-backend/branches.handler.ts
similarity index 55%
rename from apps/example-app/src/app/examples/services/mock-backend/product-stores.handler.ts
rename to apps/example-app/src/app/examples/services/mock-backend/branches.handler.ts
index e4ebce88..c9777f86 100644
--- a/apps/example-app/src/app/examples/services/mock-backend/product-stores.handler.ts
+++ b/apps/example-app/src/app/examples/services/mock-backend/branches.handler.ts
@@ -2,51 +2,48 @@ import { sortData } from '@ngrx-traits/common';
import { rest } from 'msw';
import {
- ProductsStore,
- ProductsStoreDetail,
- ProductsStoreQuery,
- ProductsStoreResponse,
+ Branch,
+ BranchDetail,
+ BranchQuery,
+ BranchResponse,
} from '../../models';
import { getRandomInteger } from '../../utils/form-utils';
-export const storeHandlers = [
- rest.get(
- '/stores',
- (req, res, ctx) => {
- let result = [...mockStores];
- const options = {
- search: req.url.searchParams.get('search'),
- sortColumn: req.url.searchParams.get('sortColumn'),
- sortAscending: req.url.searchParams.get('sortAscending'),
- skip: req.url.searchParams.get('skip'),
- take: req.url.searchParams.get('take'),
- };
- if (options?.search)
- result = mockStores.filter((entity) => {
- return options?.search
- ? entity.name.toLowerCase().includes(options?.search.toLowerCase())
- : false;
- });
- const total = result.length;
- if (options?.skip || options?.take) {
- const skip = +(options?.skip ?? 0);
- const take = +(options?.take ?? 0);
- result = result.slice(skip, skip + take);
- }
- if (options?.sortColumn) {
- result = sortData(result, {
- active: options.sortColumn as any,
- direction: options.sortAscending === 'true' ? 'asc' : 'desc',
- });
- }
- return res(ctx.status(200), ctx.json({ resultList: result, total }));
- },
- ),
- rest.get(
- '/stores/:id',
+export const branchesHandlers = [
+ rest.get('/branches', (req, res, ctx) => {
+ let result = [...mockBranches];
+ const options = {
+ search: req.url.searchParams.get('search'),
+ sortColumn: req.url.searchParams.get('sortColumn'),
+ sortAscending: req.url.searchParams.get('sortAscending'),
+ skip: req.url.searchParams.get('skip'),
+ take: req.url.searchParams.get('take'),
+ };
+ if (options?.search)
+ result = mockBranches.filter((entity) => {
+ return options?.search
+ ? entity.name.toLowerCase().includes(options?.search.toLowerCase())
+ : false;
+ });
+ const total = result.length;
+ if (options?.skip || options?.take) {
+ const skip = +(options?.skip ?? 0);
+ const take = +(options?.take ?? 0);
+ result = result.slice(skip, skip + take);
+ }
+ if (options?.sortColumn) {
+ result = sortData(result, {
+ active: options.sortColumn as any,
+ direction: options.sortAscending === 'true' ? 'asc' : 'desc',
+ });
+ }
+ return res(ctx.status(200), ctx.json({ resultList: result, total }));
+ }),
+ rest.get(
+ '/branches/:id',
(req, res, context) => {
const id = +req.params.id;
- const storeDetail = mockStoresDetails.find((value) => value.id === id);
+ const storeDetail = mockBranchesDetails.find((value) => value.id === id);
return res(context.status(200), context.json(storeDetail));
},
),
@@ -117,12 +114,12 @@ const names = [
'Wendi Ellis',
];
-const mockStoresDetails: ProductsStoreDetail[] = new Array(500)
+const mockBranchesDetails: BranchDetail[] = new Array(500)
.fill(null)
.map((_, index) => {
return {
id: index,
- name: 'SuperStore ' + index,
+ name: 'Branch ' + index,
phone:
getRandomInteger(100, 300) +
' ' +
@@ -141,11 +138,11 @@ const mockStoresDetails: ProductsStoreDetail[] = new Array(500)
manager: names[getRandomInteger(0, names.length - 1)],
departments: new Array(200).fill(null).map((value, i) => ({
id: i,
- name: 'Departament ' + i + ' of SuperStore ' + index,
+ name: 'Department ' + i + ' of Branch ' + index,
})),
};
});
-const mockStores: ProductsStore[] = mockStoresDetails.map(
+const mockBranches: Branch[] = mockBranchesDetails.map(
({ id, name, address }) => ({
id,
name,
diff --git a/apps/example-app/src/app/examples/services/mock-backend/index.ts b/apps/example-app/src/app/examples/services/mock-backend/index.ts
index 06469437..b4811237 100644
--- a/apps/example-app/src/app/examples/services/mock-backend/index.ts
+++ b/apps/example-app/src/app/examples/services/mock-backend/index.ts
@@ -1,8 +1,9 @@
import { setupWorker } from 'msw';
+
+import { branchesHandlers } from './branches.handler';
import { productHandlers } from './product.handler';
-import { storeHandlers } from './product-stores.handler';
-const worker = setupWorker(...productHandlers, ...storeHandlers);
+const worker = setupWorker(...productHandlers, ...branchesHandlers);
worker.start({
onUnhandledRequest: 'warn',
});
diff --git a/apps/example-app/src/app/examples/signals/infinete-scroll-page/components/products-branch-dropdown/products-branch-dropdown.component.ts b/apps/example-app/src/app/examples/signals/infinete-scroll-page/components/products-branch-dropdown/products-branch-dropdown.component.ts
index 1b570c03..621a12c4 100644
--- a/apps/example-app/src/app/examples/signals/infinete-scroll-page/components/products-branch-dropdown/products-branch-dropdown.component.ts
+++ b/apps/example-app/src/app/examples/signals/infinete-scroll-page/components/products-branch-dropdown/products-branch-dropdown.component.ts
@@ -13,7 +13,7 @@ import { MatOption, MatSelect } from '@angular/material/select';
import { getInfiniteScrollDataSource } from '@ngrx-traits/signals';
import { SearchOptionsComponent } from '../../../../components/search-options/search-options.component';
-import { ProductsStore } from '../../../../models';
+import { Branch } from '../../../../models';
import { ProductsBranchStore } from './products-branch.store';
@Component({
@@ -100,10 +100,10 @@ export class ProductsBranchDropdownComponent {
this.store.filterEntities({ filter: { search: query } });
}
- trackByFn(index: number, item: ProductsStore) {
+ trackByFn(index: number, item: Branch) {
return item.id;
}
- compareById(value: ProductsStore, option: ProductsStore) {
+ compareById(value: Branch, option: Branch) {
return value && option && value.id == option.id;
}
}
diff --git a/apps/example-app/src/app/examples/signals/infinete-scroll-page/components/products-branch-dropdown/products-branch.store.ts b/apps/example-app/src/app/examples/signals/infinete-scroll-page/components/products-branch-dropdown/products-branch.store.ts
index 2acd65b5..1da0ce7f 100644
--- a/apps/example-app/src/app/examples/signals/infinete-scroll-page/components/products-branch-dropdown/products-branch.store.ts
+++ b/apps/example-app/src/app/examples/signals/infinete-scroll-page/components/products-branch-dropdown/products-branch.store.ts
@@ -10,10 +10,10 @@ import { signalStore, type } from '@ngrx/signals';
import { withEntities } from '@ngrx/signals/entities';
import { lastValueFrom } from 'rxjs';
-import { ProductsStore } from '../../../../models';
-import { ProductsStoreService } from '../../../../services/products-store.service';
+import { Branch } from '../../../../models';
+import { BranchService } from '../../../../services/branch.service';
-const entity = type();
+const entity = type();
export const ProductsBranchStore = signalStore(
withEntities({
entity,
@@ -30,7 +30,7 @@ export const ProductsBranchStore = signalStore(
withEntitiesLoadingCall({
fetchEntities: async ({ entitiesPagedRequest, entitiesFilter }) => {
const res = await lastValueFrom(
- inject(ProductsStoreService).getStores({
+ inject(BranchService).getBranches({
search: entitiesFilter().search,
skip: entitiesPagedRequest().startIndex,
take: entitiesPagedRequest().size,
diff --git a/libs/ngrx-traits/core/api-docs.md b/libs/ngrx-traits/core/api-docs.md
index 55fc5c1b..e2d7ddab 100644
--- a/libs/ngrx-traits/core/api-docs.md
+++ b/libs/ngrx-traits/core/api-docs.md
@@ -389,7 +389,8 @@ it will return the cache value without calling again source
| options.expires | time to expire the cache valued, if not present is infinite
|
| options.maxCacheSize | max number of keys to store , only works if last key is variable
|
-**Example**
+**Example**
+
```js
// cache for 3 min
loadStores$ = createEffect(() => {
@@ -399,7 +400,7 @@ loadStores$ = createEffect(() => {
cache({
key: ['stores'],
store: this.store,
- source: this.storeService.getStores(),
+ source: this.storeService.getBranches(),
expire: 1000 * 60 * 3 // optional param , cache forever if not present
}).pipe(
map((res) => ProductStoreActions.loadStoresSuccess({ entities: res })),
@@ -409,7 +410,7 @@ loadStores$ = createEffect(() => {
);
});
// cache top 10, for 3 mins
- loadDepartments$ = createEffect(() => {
+loadDepartments$ = createEffect(() => {
return this.actions$.pipe(
ofType(this.localActions.loadDepartments),
concatLatestFrom(() =>
@@ -417,21 +418,34 @@ loadStores$ = createEffect(() => {
),
exhaustMap(([_, filters]) =>
cache({
- key: ['stores','departments',{ storeId: filters!.storeId },
- store: this.store,
- source: this.storeService.getStoreDepartments(filters!.storeId),
- expires: 1000 * 60 * 3,
- maxCacheSize: 10,
- }).pipe(
- map((res) =>
- this.localActions.loadDepartmentsSuccess({
- entities: res,
- })
- ),
- catchError(() => of(this.localActions.loadDepartmentsFail()))
- )
- )
- );
+ key: ['stores', 'departments', { storeId: filters!.storeId
+ },
+ store
+:
+ this.store,
+ source
+:
+ this.storeService.getBranchDepartments(filters
+ !
+.
+ storeId
+),
+ expires: 1000 * 60 * 3,
+ maxCacheSize
+:
+ 10,
+}).
+ pipe(
+ map((res) =>
+ this.localActions.loadDepartmentsSuccess({
+ entities: res,
+ })
+ ),
+ catchError(() => of(this.localActions.loadDepartmentsFail()))
+ )
+)
+)
+ ;
});
```
From 7177b49157d477176d9aa9da80b16e8180482143 Mon Sep 17 00:00:00 2001
From: Gabriel Guerrero
Date: Wed, 17 Apr 2024 22:10:54 +0100
Subject: [PATCH 2/9] test: added missing unit test to all traits
---
.../filter-entities.trait.effect.ts | 39 +--
.../ngrx-traits/signals/src/lib/test.mocks.ts | 143 +++++++++++
.../ngrx-traits/signals/src/lib/test.model.ts | 63 +++++
.../with-call-status/with-call-status.spec.ts | 43 ++++
.../lib/with-call-status/with-call-status.ts | 2 +-
.../src/lib/with-calls/with-calls.spec.ts | 37 +++
.../signals/src/lib/with-calls/with-calls.ts | 2 +-
.../with-entities-filter.util.ts | 3 +-
.../with-entities-local-filter.spec.ts | 190 ++++++++++++++
.../with-entities-remote-filter.spec.ts | 235 ++++++++++++++++++
.../with-entities-remote-filter.ts | 4 +-
.../with-entities-loading-call.spec.ts | 191 ++++++++++++++
.../with-entities-multi-selection.ts | 48 ++--
13 files changed, 949 insertions(+), 51 deletions(-)
create mode 100644 libs/ngrx-traits/signals/src/lib/test.mocks.ts
create mode 100644 libs/ngrx-traits/signals/src/lib/test.model.ts
create mode 100644 libs/ngrx-traits/signals/src/lib/with-call-status/with-call-status.spec.ts
create mode 100644 libs/ngrx-traits/signals/src/lib/with-calls/with-calls.spec.ts
create mode 100644 libs/ngrx-traits/signals/src/lib/with-entities-filter/with-entities-local-filter.spec.ts
create mode 100644 libs/ngrx-traits/signals/src/lib/with-entities-filter/with-entities-remote-filter.spec.ts
create mode 100644 libs/ngrx-traits/signals/src/lib/with-entities-loading-call/with-entities-loading-call.spec.ts
diff --git a/libs/ngrx-traits/common/src/lib/filter-entities/filter-entities.trait.effect.ts b/libs/ngrx-traits/common/src/lib/filter-entities/filter-entities.trait.effect.ts
index ca382361..cef57b8a 100644
--- a/libs/ngrx-traits/common/src/lib/filter-entities/filter-entities.trait.effect.ts
+++ b/libs/ngrx-traits/common/src/lib/filter-entities/filter-entities.trait.effect.ts
@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { TraitEffect, Type } from '@ngrx-traits/core';
+import { createEffect, ofType } from '@ngrx/effects';
import { asyncScheduler, EMPTY, of, pipe, timer } from 'rxjs';
import {
concatMap,
@@ -10,17 +11,17 @@ import {
pairwise,
startWith,
} from 'rxjs/operators';
-import { createEffect, ofType } from '@ngrx/effects';
-import {
- FilterEntitiesKeyedConfig,
- FilterEntitiesSelectors,
-} from './filter-entities.model';
+
+import { EntitiesPaginationActions } from '../entities-pagination';
import {
LoadEntitiesActions,
LoadEntitiesSelectors,
} from '../load-entities/load-entities.model';
+import {
+ FilterEntitiesKeyedConfig,
+ FilterEntitiesSelectors,
+} from './filter-entities.model';
import { ƟFilterEntitiesActions } from './filter-entities.model.internal';
-import { EntitiesPaginationActions } from '../entities-pagination';
export function createFilterTraitEffects(
allActions: ƟFilterEntitiesActions &
@@ -28,7 +29,7 @@ export function createFilterTraitEffects(
EntitiesPaginationActions,
allSelectors: FilterEntitiesSelectors &
LoadEntitiesSelectors,
- allConfigs: FilterEntitiesKeyedConfig
+ allConfigs: FilterEntitiesKeyedConfig,
): Type[] {
const traitConfig = allConfigs.filter;
@Injectable()
@@ -42,7 +43,7 @@ export function createFilterTraitEffects(
this.actions$.pipe(
ofType(allActions.filterEntities),
debounce((value) =>
- value?.forceLoad ? EMPTY : timer(debounceTime, scheduler)
+ value?.forceLoad ? of({}) : timer(debounceTime, scheduler),
),
concatMap((payload) =>
payload.patch
@@ -51,15 +52,15 @@ export function createFilterTraitEffects(
map((storedFilters) => ({
...payload,
filters: { ...storedFilters, ...payload?.filters },
- }))
+ })),
)
- : of(payload)
+ : of(payload),
),
distinctUntilChanged(
(previous, current) =>
!current?.forceLoad &&
JSON.stringify(previous?.filters) ===
- JSON.stringify(current?.filters)
+ JSON.stringify(current?.filters),
),
traitConfig?.isRemoteFilter
? pipe(
@@ -71,7 +72,7 @@ export function createFilterTraitEffects(
concatMap(([previous, current]) =>
traitConfig?.isRemoteFilter!(
previous?.filters,
- current?.filters
+ current?.filters,
)
? [
allActions.storeEntitiesFilter({
@@ -85,16 +86,16 @@ export function createFilterTraitEffects(
filters: current?.filters,
patch: current?.patch,
}),
- ]
- )
+ ],
+ ),
)
: map((action) =>
allActions.storeEntitiesFilter({
filters: action?.filters,
patch: action?.patch,
- })
- )
- )
+ }),
+ ),
+ ),
);
loadEntities$ =
@@ -108,8 +109,8 @@ export function createFilterTraitEffects(
allActions.clearEntitiesPagesCache(),
allActions.loadEntitiesFirstPage(),
]
- : [allActions.loadEntities()]
- )
+ : [allActions.loadEntities()],
+ ),
);
});
}
diff --git a/libs/ngrx-traits/signals/src/lib/test.mocks.ts b/libs/ngrx-traits/signals/src/lib/test.mocks.ts
new file mode 100644
index 00000000..c6b340e8
--- /dev/null
+++ b/libs/ngrx-traits/signals/src/lib/test.mocks.ts
@@ -0,0 +1,143 @@
+import { Product } from '../../../../../apps/example-app/src/app/examples/models';
+import { getRandomInteger } from '../../../../../apps/example-app/src/app/examples/utils/form-utils';
+
+const snes = [
+ 'Super Mario World',
+ 'F-Zero',
+ 'Pilotwings',
+ 'SimCity',
+ 'Super Tennis',
+ 'Mario Paint',
+ 'Super Mario Kart',
+ 'Super Scope 6',
+ 'BattleClash',
+ 'The Legend of Zelda: A Link to the Past',
+ 'Super Play Action Football',
+ 'NCAA Basketball',
+ 'Super Soccer',
+ 'Star Fox',
+ 'Super Mario All-Stars',
+ "Yoshi's Safari",
+ 'Vegas Stakes',
+ "Metal Combat: Falcon's Revenge",
+ 'NHL Stanley Cup',
+ 'Mario & Wario',
+ "Yoshi's Cookie",
+ 'Super Metroid',
+ 'Stunt Race FX',
+ 'Donkey Kong Country',
+ 'Ken Griffey Jr. Presents Major League Baseball',
+ 'Super Pinball: Behind the Mask',
+ 'Super Punch-Out!!',
+ 'Tin Star',
+ 'Tetris 2',
+ 'Tetris & Dr. Mario',
+ 'Uniracers',
+ "Wario's Woods",
+ 'Super Mario All Stars',
+ 'Super Mario World',
+ 'Illusion of Gaia',
+ 'Fire Emblem: Monshou no Nazo',
+ 'Mega Man 6',
+ 'EarthBound',
+ "Kirby's Dream Course",
+ "Super Mario World 2: Yoshi's Island",
+ "Donkey Kong Country 2: Diddy's Kong Quest",
+ "Kirby's Avalanche",
+ 'Killer Instinct',
+ "Mario's Super Picross",
+ 'Panel de Pon',
+ 'Super Mario RPG: Legend of the Seven Stars',
+ 'Kirby Super Star',
+ "Donkey Kong Country 3: Dixie Kong's Double Trouble!",
+ "Ken Griffey Jr.'s Winning Run",
+ 'Tetris Attack',
+ 'Fire Emblem: Seisen no Keifu',
+ 'Marvelous: Another Treasure Island',
+ 'Maui Mallard in Cold Shadow',
+ 'Arkanoid: Doh it Again',
+ "Kirby's Dream Land 3",
+ 'Heisei Shin Onigashima',
+ 'Space Invaders: The Original Game',
+ "Wrecking Crew '98",
+ 'Kirby no Kirakira Kizzu',
+ 'Sutte Hakkun',
+ 'Zoo-tto Mahjong!',
+ 'Power Soukoban',
+ 'Fire Emblem: Thracia 776',
+ 'Famicom Bunko: Hajimari no Mori',
+ 'Power Lode Runner',
+];
+const gamecube = [
+ "Luigi's Mansion",
+ 'Wave Race: Blue Storm',
+ 'Super Smash Bros. Melee',
+ 'Pikmin',
+ 'Animal Crossing',
+ "Disney's Magical Mirror Starring Mickey Mouse",
+ "Eternal Darkness: Sanity's Requiem",
+ 'Mario Party 4',
+ 'Metroid Prime',
+ 'NBA Courtside 2002',
+ 'Star Fox Adventures',
+ 'Super Mario Sunshine',
+ 'Cubivore: Survival of the Fittest',
+ 'Doshin the Giant',
+ '1080° Avalanche',
+ 'F-Zero GX',
+ 'Kirby Air Ride',
+ "The Legend of Zelda Collector's Edition",
+ 'The Legend of Zelda: Ocarina of Time Master Quest',
+ 'The Legend of Zelda: The Wind Waker',
+ 'Mario Golf: Toadstool Tour',
+ 'Mario Kart: Double Dash‼',
+ 'Mario Party 5',
+ 'Pokémon Channel',
+ 'Wario World',
+ 'GiFTPiA',
+ 'Nintendo Puzzle Collection',
+ 'Custom Robo',
+ 'Donkey Konga',
+ 'Metal Gear Solid: The Twin Snakes',
+ 'The Legend of Zelda: Four Swords Adventure',
+ 'Mario Party 6',
+ 'Mario Power Tennis',
+ 'Metroid Prime 2: Echoes',
+ 'Paper Mario: The Thousand-Year Door',
+ 'Pikmin 2',
+ 'Pokémon Box: Ruby and Sapphire',
+ 'Pokémon Colosseum',
+ 'WarioWare, Inc.: Mega Party Game$',
+ 'Final Fantasy: Crystal Chronicles',
+ 'Kururin Squash!',
+ 'Battalion Wars',
+ 'Dance Dance Revolution: Mario Mix',
+ 'Donkey Konga 2',
+ 'Donkey Kong Jungle Beat',
+ 'Fire Emblem: Path of Radiance',
+ 'Geist',
+ 'Mario Party 7',
+ 'Mario Superstar Baseball',
+ 'Pokémon XD: Gale of Darkness',
+ 'Star Fox: Assault',
+ 'Super Mario Strikers',
+ 'Densetsu no Quiz Ou Ketteisen',
+ 'Donkey Konga 3',
+ 'Chibi-Robo!',
+ 'The Legend of Zelda: Twilight Princess',
+ 'Odama',
+];
+export const mockProducts: Product[] = [
+ ...snes.map((name, id) => ({
+ name,
+ id: id + '',
+ description: 'Super Nintendo Game',
+ price: id * 2 + 10,
+ })),
+ ...gamecube.map((name, id) => ({
+ name,
+ id: snes.length + id + '',
+ description: 'GameCube Game',
+ price: id * 3 + 10,
+ })),
+];
diff --git a/libs/ngrx-traits/signals/src/lib/test.model.ts b/libs/ngrx-traits/signals/src/lib/test.model.ts
new file mode 100644
index 00000000..f3760c33
--- /dev/null
+++ b/libs/ngrx-traits/signals/src/lib/test.model.ts
@@ -0,0 +1,63 @@
+export interface Product {
+ id: string;
+ name: string;
+ description: string;
+ price: number;
+}
+export interface ProductOrder extends Product {
+ quantity?: number;
+}
+export interface ProductFilter {
+ search: string;
+}
+
+export interface ProductDetail extends Product {
+ maker: string;
+ releaseDate: string;
+ image: string;
+}
+
+export interface Branch {
+ id: number;
+ name: string;
+ address: string;
+}
+
+export interface BranchDetail {
+ id: number;
+ name: string;
+ phone: string;
+ address: {
+ line1: string;
+ postCode: string;
+ town: string;
+ country: string;
+ };
+ manager: string;
+ departments: Department[];
+}
+export type BranchQuery = {
+ search?: string | undefined;
+ sortColumn?: keyof Branch | undefined;
+ sortAscending?: string | undefined;
+ skip?: string | undefined;
+ take?: string | undefined;
+};
+export interface BranchResponse {
+ resultList: Branch[];
+ total: number;
+}
+
+export interface BranchFilter {
+ search?: string;
+}
+
+export interface Department {
+ id: number;
+ name: string;
+}
+
+export interface DepartmentFilter {
+ storeId: number;
+ search?: string;
+}
diff --git a/libs/ngrx-traits/signals/src/lib/with-call-status/with-call-status.spec.ts b/libs/ngrx-traits/signals/src/lib/with-call-status/with-call-status.spec.ts
new file mode 100644
index 00000000..bb515188
--- /dev/null
+++ b/libs/ngrx-traits/signals/src/lib/with-call-status/with-call-status.spec.ts
@@ -0,0 +1,43 @@
+import { withCallStatus } from '@ngrx-traits/signals';
+import { signalStore } from '@ngrx/signals';
+
+describe('withCallStatus', () => {
+ const Store = signalStore(withCallStatus());
+
+ it('setLoading should make isLoading return true', () => {
+ const store = new Store();
+ expect(store.isLoading()).toEqual(false);
+ store.setLoading();
+ expect(store.isLoading()).toEqual(true);
+ });
+ it('setLoaded should make isLoaded return true', () => {
+ const store = new Store();
+ expect(store.isLoaded()).toEqual(false);
+ store.setLoaded();
+ expect(store.isLoaded()).toEqual(true);
+ });
+ it('setError should make error return the object set', () => {
+ const store = new Store();
+ expect(store.error()).toEqual(undefined);
+ store.setError({ message: 'error' });
+ expect(store.error()).toEqual({ message: 'error' });
+ });
+ it('check initialValue works', () => {
+ const Store = signalStore(withCallStatus({ initialValue: 'loading' }));
+ const store = new Store();
+ expect(store.isLoading()).toEqual(true);
+ });
+ it('check prop rename works', () => {
+ const Store = signalStore(withCallStatus({ prop: 'test' }));
+ const store = new Store();
+ expect(store.isTestLoading()).toEqual(false);
+ store.setTestLoading();
+ expect(store.isTestLoading()).toEqual(true);
+ expect(store.isTestLoaded()).toEqual(false);
+ store.setTestLoaded();
+ expect(store.isTestLoaded()).toEqual(true);
+ expect(store.testError()).toEqual(undefined);
+ store.setTestError({ message: 'error' });
+ expect(store.testError()).toEqual({ message: 'error' });
+ });
+});
diff --git a/libs/ngrx-traits/signals/src/lib/with-call-status/with-call-status.ts b/libs/ngrx-traits/signals/src/lib/with-call-status/with-call-status.ts
index 519ecf83..95021016 100644
--- a/libs/ngrx-traits/signals/src/lib/with-call-status/with-call-status.ts
+++ b/libs/ngrx-traits/signals/src/lib/with-call-status/with-call-status.ts
@@ -136,7 +136,7 @@ export function withCallStatus({
[loadedKey]: computed(() => callState() === 'loaded'),
[errorKey]: computed(() => {
const v = callState();
- return typeof v === 'object' ? v.error : null;
+ return typeof v === 'object' ? v.error : undefined;
}),
};
}),
diff --git a/libs/ngrx-traits/signals/src/lib/with-calls/with-calls.spec.ts b/libs/ngrx-traits/signals/src/lib/with-calls/with-calls.spec.ts
new file mode 100644
index 00000000..cbca4504
--- /dev/null
+++ b/libs/ngrx-traits/signals/src/lib/with-calls/with-calls.spec.ts
@@ -0,0 +1,37 @@
+import { TestBed } from '@angular/core/testing';
+import { withCalls } from '@ngrx-traits/signals';
+import { signalStore, withState } from '@ngrx/signals';
+import { Subject, throwError } from 'rxjs';
+
+describe('withCalls', () => {
+ const apiResponse = new Subject();
+ const Store = signalStore(
+ withState({ foo: 'bar' }),
+ withCalls(() => ({
+ testCall: ({ ok }: { ok: boolean }) => {
+ return ok ? apiResponse : throwError(() => new Error('fail'));
+ },
+ })),
+ );
+ it('Successful call should set status to loading and loaded ', async () => {
+ TestBed.runInInjectionContext(() => {
+ const store = new Store();
+ expect(store.isTestCallLoading()).toBeFalsy();
+ store.testCall({ ok: true });
+ expect(store.isTestCallLoading()).toBeTruthy();
+ apiResponse.next('test');
+ expect(store.isTestCallLoaded()).toBeTruthy();
+ expect(store.testCallResult()).toBe('test');
+ });
+ });
+ it('Fail on a call should set status return error ', async () => {
+ TestBed.runInInjectionContext(() => {
+ const store = new Store();
+ expect(store.isTestCallLoading()).toBeFalsy();
+ store.testCall({ ok: false });
+ console.log(store.testCallCallStatus());
+ expect(store.testCallError()).toEqual(new Error('fail'));
+ expect(store.testCallResult()).toBe(undefined);
+ });
+ });
+});
diff --git a/libs/ngrx-traits/signals/src/lib/with-calls/with-calls.ts b/libs/ngrx-traits/signals/src/lib/with-calls/with-calls.ts
index 0bc5d071..980709fe 100644
--- a/libs/ngrx-traits/signals/src/lib/with-calls/with-calls.ts
+++ b/libs/ngrx-traits/signals/src/lib/with-calls/with-calls.ts
@@ -182,7 +182,7 @@ export function withCalls<
} as StateSignal