Use store for formations feature

This commit is contained in:
Jim Martens 2023-11-14 23:08:16 +01:00
parent 527f8592c5
commit f7e0ac0626
12 changed files with 242 additions and 16 deletions

View File

@ -1,5 +1,9 @@
import {Routes} from '@angular/router';
import {AppAuthGuard} from "./auth/auth.guard";
import {provideState} from "@ngrx/store";
import {featureStateName as formationsFeature, formationsReducers} from "./formations/store";
import {provideEffects} from "@ngrx/effects";
import {FormationsEffects} from "./formations/store/formations.effects";
export const ROOT_ROUTES: Routes = [
{
@ -46,7 +50,11 @@ export const ROOT_ROUTES: Routes = [
{
path: 'formations',
loadComponent: () => import("./formations/formations.component").then(mod => mod.FormationsComponent),
canActivate: [AppAuthGuard]
canActivate: [AppAuthGuard],
providers: [
provideState(formationsFeature, formationsReducers),
provideEffects([FormationsEffects])
],
},
{
path: '',

View File

@ -72,7 +72,7 @@
<ion-label i18n class="bold ion-text-end">Length</ion-label>
</ion-item>
<ion-item-sliding>
<ion-item *ngFor="let formation of formations">
<ion-item *ngFor="let formation of formations$ | async">
<ion-label>{{ formation.name }}</ion-label>
<ion-label>{{ formation.formation }}</ion-label>
<ion-label class="ion-text-end">{{ formation.length }}</ion-label>

View File

@ -1,4 +1,4 @@
import {Component} from '@angular/core';
import {Component, inject, OnInit} from '@angular/core';
import {
IonButton,
IonButtons,
@ -32,7 +32,6 @@ import {
addSharp,
mapOutline,
mapSharp,
time,
timeOutline,
timeSharp,
trainOutline,
@ -41,8 +40,10 @@ import {
trashSharp
} from "ionicons/icons";
import {Route} from "../routes/model/route";
import {Formation} from "../formations/model/formation";
import {Timetable} from ".././timetables/model/timetable";
import {allFormations, FormationsState} from "../formations/store";
import {Store} from "@ngrx/store";
import {loadAllFormationsAction} from "../formations/store/formations.actions";
@Component({
selector: 'app-dashboard',
@ -77,7 +78,8 @@ import {Timetable} from ".././timetables/model/timetable";
IonFooter
]
})
export class DashboardComponent {
export class DashboardComponent implements OnInit {
private readonly formationsStore: Store<FormationsState> = inject(Store<FormationsState>);
loggedIn$: Observable<boolean>;
routes: Route[] = [
{
@ -89,7 +91,7 @@ export class DashboardComponent {
}
];
timetables: Timetable[] = [];
formations: Formation[] = [];
formations$ = this.formationsStore.select(allFormations());
constructor(private readonly keycloakService: KeycloakService) {
this.loggedIn$ = from(this.keycloakService.isLoggedIn());
@ -108,5 +110,7 @@ export class DashboardComponent {
});
}
protected readonly time = time;
ngOnInit() {
this.formationsStore.dispatch(loadAllFormationsAction());
}
}

View File

@ -14,7 +14,7 @@
<ion-label i18n class="bold ion-text-end">Length</ion-label>
</ion-item>
<ion-item-sliding>
<ion-item *ngFor="let formation of formations">
<ion-item *ngFor="let formation of formations$ | async">
<ion-label>{{ formation.name }}</ion-label>
<ion-label>{{ formation.formation }}</ion-label>
<ion-label class="ion-text-end">{{ formation.length }}</ion-label>

View File

@ -1,4 +1,4 @@
import {Component} from '@angular/core';
import {Component, inject, OnInit} from '@angular/core';
import {
IonButtons,
IonContent,
@ -20,8 +20,10 @@ import {
} from "@ionic/angular/standalone";
import {addIcons} from "ionicons";
import {addOutline, addSharp, trashOutline, trashSharp} from "ionicons/icons";
import {Formation} from "./model/formation";
import {NgForOf} from "@angular/common";
import {AsyncPipe, NgForOf} from "@angular/common";
import {allFormations, FormationsState} from "./store";
import {Store} from "@ngrx/store";
import {loadAllFormationsAction} from "./store/formations.actions";
@Component({
selector: 'app-formations',
@ -46,12 +48,13 @@ import {NgForOf} from "@angular/common";
IonFab,
IonFabButton,
IonFooter,
NgForOf
NgForOf,
AsyncPipe
]
})
export class FormationsComponent {
formations: Formation[] = [];
export class FormationsComponent implements OnInit {
private readonly store: Store<FormationsState> = inject(Store<FormationsState>);
formations$ = this.store.select(allFormations());
constructor() {
addIcons({
@ -62,4 +65,8 @@ export class FormationsComponent {
});
}
ngOnInit() {
this.store.dispatch(loadAllFormationsAction());
}
}

View File

@ -0,0 +1,16 @@
import {TestBed} from '@angular/core/testing';
import {FormationsService} from './formations.service';
describe('FormationsService', () => {
let service: FormationsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(FormationsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,63 @@
import {Injectable} from '@angular/core';
import {Formation} from "./model/formation";
import {catchError, Observable, of} from "rxjs";
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {environment} from "../../environments/environment";
import {MessageType} from "../messages/model/message-type";
import {MessagesService} from "../messages/messages.service";
@Injectable({
providedIn: 'root'
})
export class FormationsService {
httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
private formationsURL = environment.backendURL + '/formation';
constructor(private readonly http: HttpClient,
private readonly messageService: MessagesService) {
}
fetchFormations(): Observable<Formation[]> {
return this.http.get<Formation[]>(this.formationsURL, this.httpOptions)
.pipe(
catchError(this.handleError<Formation[]>('fetchFormations', []))
);
}
/**
* Handle Http operation that failed.
* Let the app continue.
* @param operation - name of the operation that failed
* @param result - optional value to return as the observable result
*/
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
if (error.status == 0) {
this.log(MessageType.UNKNOWN_ERROR);
}
if (error.status == 401) {
this.log(MessageType.UNAUTHENTICATED);
}
if (error.status == 403) {
this.log(MessageType.UNAUTHORIZED);
}
if (error.status == 500) {
this.log(MessageType.INTERNAL_SERVER_ERROR);
}
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
private log(type: MessageType) {
this.messageService.logMessage('FormationsService', type);
}
}

View File

@ -3,3 +3,10 @@ export interface Formation {
formation: string;
length: number;
}
export const DEFAULT_FORMATION: Formation = {
name: "",
formation: "",
length: 0,
}

View File

@ -0,0 +1,48 @@
import {createAction, props} from "@ngrx/store";
import {Formation} from "../model/formation";
export enum ActionTypes {
LoadAllFormations = '[Formations] Load All Formations',
LoadAllFormationsFinished = '[Formations] Load All Formations Finished',
LoadSingleFormation = '[Formations] Load Single Formation',
LoadSingleFormationFinished = '[Formations] Load Single Formation Finished',
AddFormation = '[Formations] Add Formation',
UpdateFormation = '[Formations] Update Formation',
DeleteFormation = '[Formations] Delete Formation',
}
export const loadAllFormationsAction = createAction(
ActionTypes.LoadAllFormations
);
export const loadAllFormationsFinishedAction = createAction(
ActionTypes.LoadAllFormationsFinished,
props<{ payload: Formation[] }>()
);
export const loadSingleFormationAction = createAction(
ActionTypes.LoadSingleFormation,
props<{ payload: string }>()
);
export const loadSingleFormationFinishedAction = createAction(
ActionTypes.LoadSingleFormationFinished,
props<{ payload: Formation }>()
);
export const addFormationAction = createAction(
ActionTypes.AddFormation,
props<{ payload: Formation }>()
);
export const updateFormationAction = createAction(
ActionTypes.UpdateFormation,
props<{ payload: Formation }>()
);
export const deleteFormation = createAction(
ActionTypes.DeleteFormation,
props<{ payload: Formation }>()
);

View File

@ -0,0 +1,18 @@
import {inject, Injectable} from "@angular/core";
import {Actions, createEffect, ofType} from "@ngrx/effects";
import {loadAllFormationsAction, loadAllFormationsFinishedAction} from "./formations.actions";
import {map, switchMap} from "rxjs";
import {FormationsService} from "../formations.service";
@Injectable()
export class FormationsEffects {
loadAllFormations$ = createEffect(
(actions$ = inject(Actions), formationsService = inject(FormationsService)) => {
return actions$.pipe(
ofType(loadAllFormationsAction),
switchMap(() => formationsService.fetchFormations()),
map(formations => loadAllFormationsFinishedAction({payload: formations}))
)
},
{functional: true});
}

View File

@ -0,0 +1,27 @@
import {DEFAULT_FORMATION, Formation} from "../model/formation";
import {createReducer, on} from "@ngrx/store";
import {loadAllFormationsFinishedAction, loadSingleFormationFinishedAction} from "./formations.actions";
export interface ReducerFormationsState {
items: Formation[];
selectedItem: Formation;
}
export const initialState: ReducerFormationsState = {
items: [],
selectedItem: DEFAULT_FORMATION,
};
export const formationsReducer = createReducer(
initialState,
on(loadAllFormationsFinishedAction, (state,
action) => ({
...state,
items: [...action.payload]
})),
on(loadSingleFormationFinishedAction, (state,
action) => ({
...state,
selectedItem: action.payload
})),
);

View File

@ -0,0 +1,28 @@
import {formationsReducer, ReducerFormationsState} from "./formations.reducer";
import {ActionReducerMap, createFeatureSelector, createSelector} from "@ngrx/store";
export const featureStateName = 'formationsFeature';
export interface FormationsState {
formations: ReducerFormationsState;
}
export const formationsReducers: ActionReducerMap<FormationsState> = {
formations: formationsReducer,
};
// extract the main property 'formationsFeature' from the state object
export const getFormationsFeatureState = createFeatureSelector<FormationsState>(
featureStateName
);
export const allFormations = () => createSelector(
getFormationsFeatureState,
(state: FormationsState) => state.formations.items
);
export const selectedFormation = () => createSelector(
getFormationsFeatureState,
(state: FormationsState) =>
state.formations.selectedItem
);