Skip to content

Commit

Permalink
refactor(business): introduce ngrx signals
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelschoenbaechler committed Feb 19, 2024
1 parent 7d68ffe commit dff7ff8
Show file tree
Hide file tree
Showing 14 changed files with 522 additions and 386 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"root": true,
"ignorePatterns": ["projects/**/*"],
"settings": {
"import/resolver": {
"typescript": {}
}
},
"overrides": [
{
"files": ["*.ts"],
Expand Down
77 changes: 77 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"@ionic/angular": "^7.6.4",
"@ionic/storage-angular": "^4.0.0",
"@ngneat/until-destroy": "^10.0.0",
"@ngrx/operators": "^17.1.0",
"@ngrx/signals": "^17.1.0",
"ionicons": "^7.2.2",
"lodash": "^4.17.21",
"rxjs": "~7.8.1",
Expand All @@ -57,6 +59,7 @@
"@typescript-eslint/eslint-plugin": "6.18.1",
"@typescript-eslint/parser": "6.18.1",
"eslint": "^8.56.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "48.0.2",
"eslint-plugin-unused-imports": "^3.0.0",
Expand Down
208 changes: 208 additions & 0 deletions src/app/business/business.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import {
getState,
patchState,
signalStore,
withComputed,
withHooks,
withMethods,
withState
} from '@ngrx/signals';
import { Business, BusinessStatus, BusinessType } from 'swissparl';
import { computed, inject } from '@angular/core';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { BusinessFilter, BusinessService } from './services/business.service';
import { filter, pipe, switchMap, tap } from 'rxjs';
import { map } from 'rxjs/operators';
import { tapResponse } from '@ngrx/operators';
import * as _ from 'lodash';

type BusinessState = {
businesses: Business[];
businessTypes: BusinessType[];
businessStatuses: BusinessStatus[];
selectedBusinessId: number | null;
hasNoContent: boolean;
isLoading: boolean;
isLoadingMore: boolean;
isRefreshing: boolean;
hasFilterError: boolean;
hasError: boolean;
query: BusinessFilter;
};

const initialState: BusinessState = {
businesses: [],
businessTypes: [],
businessStatuses: [],
selectedBusinessId: null,
hasNoContent: false,
isLoading: false,
isLoadingMore: false,
isRefreshing: false,
hasError: false,
hasFilterError: false,
query: {
top: 20,
skip: 0,
searchTerm: '',
businessTypes: [],
businessStatuses: []
}
};

export const BusinessStore = signalStore(
{ providedIn: 'root' },
withState(initialState),
withComputed(({ businesses, selectedBusinessId }) => ({
selectedBusiness: computed(() =>
businesses().find((b) => b.ID === selectedBusinessId())
)
})),
withMethods((store, businessService = inject(BusinessService)) => ({
loadBusinesses: rxMethod<BusinessFilter>(
pipe(
tap((query) =>
patchState(store, (state) => ({
isLoading: state.businesses.length === 0,
hasError: false,
hasNoContent: false
}))
),
switchMap((query) =>
businessService.getBusinesses(query).pipe(
tapResponse({
next: (businesses) =>
patchState(store, (state) => ({
businesses: state.isRefreshing
? businesses
: [...state.businesses, ...businesses],
hasNoContent: businesses.length === 0
})),
error: () => patchState(store, { hasError: true }),
finalize: () =>
patchState(store, {
isLoading: false,
isLoadingMore: false,
isRefreshing: false
})
})
)
)
)
),

loadMore: () => {
patchState(store, (state) => ({
isLoadingMore: true,
query: {
...state.query,
skip: state.query.skip + state.query.top
}
}));
},

refresh: () => {
patchState(store, (state) => ({
isRefreshing: true,
query: {
...state.query,
skip: 0
}
}));
},

updateQuery: (query: BusinessFilter) => {
patchState(store, (state) => ({
businesses: [],
query: { ...state.query, ...query }
}));
},

resetQuery: () => {
patchState(store, () => ({
query: initialState.query
}));
},

selectBusiness: rxMethod<number>(
pipe(
filter((id) => {
const state = getState(store);
const isLoaded = state.businesses.find((b) => b.ID === id);

if (isLoaded) {
patchState(store, { selectedBusinessId: id });
}

return !isLoaded;
}),
tap(() =>
patchState(store, {
isLoading: true,
hasError: false
})
),
switchMap((id) =>
businessService.getBusiness(id).pipe(
tapResponse({
next: (business) =>
patchState(store, {
businesses: [business],
selectedBusinessId: business.ID
}),
error: () => patchState(store, { hasError: true }),
finalize: () => patchState(store, { isLoading: false })
})
)
)
)
),

loadBusinessStates: rxMethod(
pipe(
tap(() =>
patchState(store, {
hasFilterError: false
})
),
switchMap(() =>
businessService.getBusinessStatus().pipe(
map((businessStates) =>
_.uniqBy(businessStates, 'BusinessStatusId')
), // TODO move _.uniqBy to service
tapResponse({
next: (businessStatuses) =>
patchState(store, { businessStatuses }),
error: () => patchState(store, { hasFilterError: true })
})
)
)
)
),

loadBusinessTypes: rxMethod(
pipe(
tap(() =>
patchState(store, {
hasFilterError: false
})
),
switchMap(() =>
businessService.getBusinessTypes().pipe(
tapResponse({
next: (businessTypes) => patchState(store, { businessTypes }),
error: () => patchState(store, { hasFilterError: true })
})
)
)
)
)
})),

withHooks({
onInit: ({ loadBusinesses, loadBusinessStates, loadBusinessTypes }) => {
loadBusinessStates(null);
loadBusinessTypes(null);
}
})
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<form (ngSubmit)="onSubmit()">
<h3>Geschäftstyp</h3>
<ion-list>
<ion-item *ngFor="let businessType of businessTypeCheckboxes()">
<ion-checkbox
labelPlacement="start"
[name]="businessType.BusinessTypeName"
[(ngModel)]="businessType.checked"
>{{ businessType.BusinessTypeName }}</ion-checkbox
>
</ion-item>
</ion-list>
<h3>Geschäftsstatus</h3>
<ion-list>
<ion-item *ngFor="let businessStatus of businessStatusCheckboxes()">
<ion-checkbox
labelPlacement="start"
[name]="businessStatus.BusinessStatusName"
[(ngModel)]="businessStatus.checked"
>{{ businessStatus.BusinessStatusName }}</ion-checkbox
>
</ion-item>
</ion-list>

<div class="floating-button">
<ion-button expand="block" type="submit">Filter anwenden</ion-button>
</div>
</form>


Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.floating-button {
position: fixed;
bottom: 1rem;
left: 0;
right: 0;
z-index: 999;
}

ion-list:last-child {
padding-bottom: 4rem;
}
Loading

0 comments on commit dff7ff8

Please sign in to comment.