put in the ngrx

This commit is contained in:
2026-03-12 18:29:12 +11:00
parent c5e641180b
commit d1d5d88f9c
13 changed files with 233 additions and 48 deletions
+44
View File
@@ -14,6 +14,9 @@
"@angular/forms": "^21.0.0",
"@angular/platform-browser": "^21.0.0",
"@angular/router": "^21.0.0",
"@ngrx/effects": "^21.0.1",
"@ngrx/store": "^21.0.1",
"@ngrx/store-devtools": "^21.0.1",
"@primeuix/themes": "^2.0.2",
"@tailwindcss/postcss": "^4.1.17",
"file-saver": "^2.0.5",
@@ -2611,6 +2614,47 @@
"@tybys/wasm-util": "^0.10.1"
}
},
"node_modules/@ngrx/effects": {
"version": "21.0.1",
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-21.0.1.tgz",
"integrity": "sha512-hSdpToAiSYa5FJ/CAygQHpnCaF2S1HO7q/57ob3XvNTWmkofa0VqI/IIe4W57bojh2YOWCJ91SCn3kAjymaV3g==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/core": "^21.0.0",
"@ngrx/store": "21.0.1",
"rxjs": "^6.5.3 || ^7.5.0"
}
},
"node_modules/@ngrx/store": {
"version": "21.0.1",
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-21.0.1.tgz",
"integrity": "sha512-2hGnw/c5o8nmKzyx7TrUUM7FXjE2zqjX0EF+wLbw9Oy/L+VdCmx+ZI1BFjuAR4B8PKEWHG2KSbOM13SMNkpZiA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/core": "^21.0.0",
"rxjs": "^6.5.3 || ^7.5.0"
}
},
"node_modules/@ngrx/store-devtools": {
"version": "21.0.1",
"resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-21.0.1.tgz",
"integrity": "sha512-G9fO7CFwYUpz8+JZ9uny+lVJ7iv6PcFJDg+jae5CCrAUIiflnR8gbwD8exAd2AODpxPCpmnSojuLNpLDAcwGzQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/core": "^21.0.0",
"@ngrx/store": "21.0.1",
"rxjs": "^6.5.3 || ^7.5.0"
}
},
"node_modules/@npmcli/agent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz",
+4 -1
View File
@@ -29,6 +29,9 @@
"@angular/forms": "^21.0.0",
"@angular/platform-browser": "^21.0.0",
"@angular/router": "^21.0.0",
"@ngrx/effects": "^21.0.1",
"@ngrx/store": "^21.0.1",
"@ngrx/store-devtools": "^21.0.1",
"@primeuix/themes": "^2.0.2",
"@tailwindcss/postcss": "^4.1.17",
"file-saver": "^2.0.5",
@@ -51,4 +54,4 @@
"typescript": "~5.9.2",
"vitest": "^4.0.8"
}
}
}
BIN
View File
Binary file not shown.
+45 -11
View File
@@ -8,23 +8,57 @@ import { routes } from './app.routes';
import MyPreset from './mythem';
import { JwtInterceptor, ErrorInterceptor, initializeApp } from './shares';
import { provideStore } from '@ngrx/store';
import { provideStoreDevtools } from '@ngrx/store-devtools';
import { isDevMode } from '@angular/core';
import { provideEffects } from '@ngrx/effects';
import { staffEffects,staffsReducer } from './state';
export const appConfig: ApplicationConfig = {
providers: [
provideStore({
myStaffs: staffsReducer,
}),
provideStoreDevtools({
maxAge: 25,
logOnly: !isDevMode()
}),
provideBrowserGlobalErrorListeners(),
MessageService, ConfirmationService,
provideAppInitializer(initializeApp()),
provideHttpClient(withInterceptors([JwtInterceptor, ErrorInterceptor])),
MessageService, ConfirmationService,
provideAppInitializer(initializeApp()),
provideHttpClient(withInterceptors([JwtInterceptor, ErrorInterceptor])),
provideRouter(routes, withComponentInputBinding()),
providePrimeNG({
theme: {
preset: MyPreset,
options: {
cssLayer: {
providePrimeNG({
theme: {
preset: MyPreset,
options: {
cssLayer: {
name: 'primeng',
order: 'theme, base, primeng'
}
}
}
}),
]
}
}),
provideEffects([staffEffects])
]
};
/*
import { Store } from '@ngrx/store';
private readonly store = inject(Store);
protected books = this.store.selectSignal(selectBooks);
protected onAdd(id: number) {
this.store.dispatch(StaffsActions.addStaff({ id }));
}
protected onRemove(id: number) {
this.store.dispatch(StaffsActions.removeStaff({ id }));
}
on ngOnInit() {
use it normal get from API first
and subscribe( (x) => this.store.dispatch(StaffApiAction.LoadStaffList({x})))
}
*/
+9 -11
View File
@@ -85,15 +85,16 @@ export class PersonEdit implements OnInit, OnDestroy {
alive: [false], //Validators.required
fatherId:[0],
motherId:[0]
});
constructor( private cdr: ChangeDetectorRef,public dialogService: DialogService,
public ref: DynamicDialogRef, public config: DynamicDialogConfig,
private router: Router, private route: ActivatedRoute,
private appSetting: AppSettingService,
constructor(
private cdr: ChangeDetectorRef,
public dialogService: DialogService,
public ref: DynamicDialogRef,
public config: DynamicDialogConfig,
private router: Router,
private route: ActivatedRoute,
private appSetting: AppSettingService,
) {
this.hostsite = this.appSetting.appSetting.attachment;
}
@@ -118,11 +119,8 @@ constructor( private cdr: ChangeDetectorRef,public dialogService: DialogService,
this.motherList.push(item);
else
this.fatherList.push(item);
}
}
}
}
getClassForRequire(prev:string,name:string){
+2 -1
View File
@@ -28,7 +28,8 @@
</form>
</div>
<div>
<p-table [value]="userList" sortMode="multiple"
<!--[value]="userList"-->
<p-table [value]="users" sortMode="multiple"
class="p-datatable-sm" [loading]="loading">
<ng-template pTemplate="header">
<tr>
+30 -24
View File
@@ -2,13 +2,8 @@ import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, inject, ChangeDe
import { StaffView ,StaffSearch } from '../models';
/* ngRx */
/*
import { Store, select } from '@ngrx/store';
import * as validationActions from './store/actions/validationpoint.action';
import * as validationSelector from './store/selectors/validationpoint.selector';
import { appValidationState } from './store/reducers';
*/
import { Store } from '@ngrx/store';
import { StaffsActions, StaffsApiActions } from '../state/staff.actions';
import { take } from 'rxjs/operators';
import { Subscription } from 'rxjs';
import { Router } from '@angular/router';
@@ -19,6 +14,7 @@ import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { selectStaffLoaded,selectState } from '../state/staff.selectors';
@Component({
selector: 'staff-list',
@@ -28,13 +24,16 @@ import { InputTextModule } from 'primeng/inputtext';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class StaffComponent implements OnInit, OnDestroy{
private readonly store = inject(Store);
private subscription:Subscription = new Subscription();
firstname = '';
email = '';
lastname = '';
loading = false;
userList:StaffView[] = [];
appState = this.store.selectSignal(selectState);
users = this.appState().staffs;
staffloadyet = this.appState().staffloaded;
private cd = inject(ChangeDetectorRef);
msg ="[Staff component]";
@@ -99,25 +98,32 @@ export class StaffComponent implements OnInit, OnDestroy{
const canSearch = true; // this.canSearch();
if (canSearch)
{
this.loading = true;
const criteria = this.getSearchCiteria();
this.staffService.searchCriteria = criteria;
this.subscription.add(
this.staffService.searchStaffs(criteria).subscribe( {
next: result => {
this.loading = false;
// console.log(this.msg + "search load Data", result);
this.userList = result.data;
this.cd.detectChanges();
},
error: e => {
const message = e || e.message;
// this.toastr.error(message);
this.loading = false;
console.log("error ", e);
console.log("search function store staffload yet",this.staffloadyet, this.users);
if (this.staffloadyet != true) {
this.loading = true;
const loadStaff$ = this.staffService.searchStaffs(criteria);
this.subscription.add(
loadStaff$.subscribe( {
next: result => {
this.loading = false;
this.userList = result.data;
this.store.dispatch(StaffsApiActions.loadStaffSuccess({staffs:this.userList, staffLoad: true}));
this.cd.detectChanges();
},
error: e => {
const message = e || e.message;
// this.toastr.error(message);
this.loading = false;
console.log("error ", e);
}
})
);
}
})
);
}
}
newUser():void {
+6
View File
@@ -0,0 +1,6 @@
import { StaffView } from '../models';
export interface AppState {
staffs: Array<StaffView>;
staffloaded : boolean;
}
+5
View File
@@ -0,0 +1,5 @@
export * from './app.state';
export * from './staff.actions';
export * from './staff.effects';
export * from './staff.reducer';
export * from './staff.selectors';
+19
View File
@@ -0,0 +1,19 @@
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { StaffSearch, StaffView } from '../models';
export const StaffsActions = createActionGroup({
source: 'Staffs',
events: {
'Add Staff': props<{ staffId: number }>(),
'Remove Staff': props<{ staffId: number }>(),
'Load Staff': props<{criteria: StaffSearch}>(),
},
});
export const StaffsApiActions = createActionGroup({
source: 'Staffs API',
events: {
'LoadStaffSuccess': props<{ staffs: Array<StaffView>; staffLoad: boolean }>(),
'LoadStaffFail': props<{ error: string }>(),
},
});
+29
View File
@@ -0,0 +1,29 @@
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, filter, map, of, switchMap, exhaustMap } from 'rxjs';
//import { concatLatestFrom } from '@ngrx/operators';
import { HttpErrorResponse } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { StaffsActions, StaffsApiActions } from './staff.actions';
import { StaffService } from '../staff/staff.service';
import { StaffSearch } from '../models';
@Injectable()
export class staffEffects {
private actions$: Actions = inject(Actions);
private staffService: StaffService = inject(StaffService);
private store: Store = inject(Store);
LoadStaffs$ = createEffect(() =>
this.actions$.pipe(
ofType(StaffsActions.loadStaff),
exhaustMap((action: {criteria:StaffSearch}) =>
this.staffService.searchStaffs(action.criteria).pipe(
map((response) => StaffsApiActions.loadStaffSuccess({staffs: response.data, staffLoad: true})),
catchError((err: HttpErrorResponse) => of(StaffsApiActions.loadStaffFail({ error: err.error})))
)
)
));
}
+24
View File
@@ -0,0 +1,24 @@
import { createReducer, on } from '@ngrx/store';
import { StaffsApiActions } from './staff.actions';
import { StaffView } from '../models';
import { AppState } from './app.state';
export const initialState: AppState ={
staffs:[],
staffloaded: false
}
export const staffsReducer = createReducer(
initialState,
on(StaffsApiActions.loadStaffSuccess, (
state, action) => {
return {
...state,
staffs: action.staffs,
staffloaded: true
}
}
)
);
+16
View File
@@ -0,0 +1,16 @@
import { createSelector, createFeatureSelector } from '@ngrx/store';
import { StaffView } from '../models';
import { AppState } from './app.state';
export const selectState = createFeatureSelector<AppState>('myStaffs');
// 2. Create a selector for a specific piece of state
export const selectStaff = createSelector(
selectState,
(state: AppState) => state.staffs
);
// 2. Create a selector for a specific piece of state
export const selectStaffLoaded = createSelector(
selectState,
(state: AppState) => state.staffloaded);