{"id":18768555,"url":"https://github.com/dacili/ngrx","last_synced_at":"2026-05-08T01:40:30.011Z","repository":{"id":113888275,"uuid":"580822830","full_name":"Dacili/NgRx","owner":"Dacili","description":"Personally not a fan of a state management, but let's play with it...","archived":false,"fork":false,"pushed_at":"2025-01-22T18:53:57.000Z","size":7490,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-22T19:41:29.658Z","etag":null,"topics":["angular","json-fake-api","ngrx","redux","redux-devtools-extension","state-management"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dacili.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-21T14:38:39.000Z","updated_at":"2025-01-22T18:54:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"7eda5ec3-3fe9-4d3d-8c02-4531052117c7","html_url":"https://github.com/Dacili/NgRx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dacili%2FNgRx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dacili%2FNgRx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dacili%2FNgRx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dacili%2FNgRx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dacili","download_url":"https://codeload.github.com/Dacili/NgRx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239671416,"owners_count":19677873,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","json-fake-api","ngrx","redux","redux-devtools-extension","state-management"],"created_at":"2024-11-07T19:13:08.217Z","updated_at":"2025-12-10T05:30:18.032Z","avatar_url":"https://github.com/Dacili.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NgRx\n\n**NgRx** (https://ngrx.io/) Store provides reactive state management for Angular apps inspired by Redux. NgRx Store is mainly for managing global state across an entire application.   \n* Use selectors for getting the immutable properties from the store,   \n* activate (dispatch) actions   \n* to trigger some update on state (reducers),   \n* or to hit the APIs to get or save the data (effects).\n\n ![image](https://github.com/user-attachments/assets/6c351fcf-4352-4c98-bf73-2d7cf7bcac05)  \n\n---\n\nNote: this code use the base code from this repo https://github.com/Dacili/Nswag-client-generation-with-Swagger, with additions for NgRx  \nHow to run this solution:\n- *apiSignalR* is the backend project (.Net Core 6) - Focus in the code was on the frontend app, so the backend is not needed\n- *frontendSignalRAngular* is the frontend project (Angular 13) - run it via cmd, with ng serve  \n\nGif of the test app and functionlities:  \n![ngrx gif medi](https://user-images.githubusercontent.com/37112852/210093937-70d5cf15-a55b-4614-a6a8-bd14c1ddea4c.gif)  \n\nYou will need these pkgs:  \n```  \n \"@ngrx/effects\": \"^13.0.0\",\n \"@ngrx/store\": \"^13.0.0\",\n \"@ngrx/store-devtools\": \"^13.0.0\",\n```   \n\u003e **Note:** Make sure that you have same version of these, as you have the angular app (\"@angular/core\": \"~13.0.0\"), so you don't get diff kind of errors.  \n  \nFor **scaffolding** the code with cmd, install this pkg:   \n```  \nnpm i @ngrx/schematics@13.0.0     \n```  \nand then \n```     \nng add @ngrx/schematics\n```  \nor with this cmd:    \n```     \nng config cli.defaultCollection @ngrx/schematics\n```  \nWithout that, you will get error like:   ![image](https://user-images.githubusercontent.com/37112852/210547326-93518860-357f-4b01-ac7d-f7f6549dae82.png)\n  \nIf you connected well, you will be able to run:  \n``` \nng generate action state/actions/Medii \n``` \nIt will generate the code for new action.   \n\n\nOrganization of the files for ngrx:  \n![image](https://user-images.githubusercontent.com/37112852/210094362-991226c0-1594-4654-a432-8975c100c82c.png)\n\nThe UI is in *app.component.html*, and the logic is in *app.component.ts.*  \nThe **main state** is the AppState. \n```\nexport interface AppState {\n  counterState: CounterState;\n  userState: UserState;\n  usersState: UsersState;\n}\n```  \nThe classic way for **selectors** is:  \n```\nexport const getCount_Selector = createSelector(\n  (state: AppState) =\u003e state.counterState,\n  (state: CounterState) =\u003e state.counter\n);\n```  \nThe **feature** way for selectors is:  \n```\nexport const usersFeatureKey = 'Users Feature';\nexport const selectUsersFeature = createFeatureSelector\u003cUsersState\u003e(\n  usersFeatureKey\n);\n\nexport const getAllUsers_Selector = createSelector(\n  selectUsersFeature,\n  (state: UsersState) =\u003e state.users\n);\n```  \nNotice *createFeatureSelector\u003c**UsersState**\u003e* that we're providing state interface for which one is this feature   \n\nClassic **action, with no params**: \n```  \nexport const increment_Action = createAction('[Counter] increment');\n```   \n**Action, with params, with props**: \n```  \nexport const loginUser_Action = createAction('[User] saveUser', props\u003c{\n  username: string;\n  password: string;\n}\u003e()\n);\n```  \n**Action, with params, without props**: \n```  \nexport const deleteUserById_Action = createAction(\n  '[Users] delete user by id',\n  (id: number) =\u003e ({ id })\n);\n```  \n\nCheck also different types of reducers, also without or with params.\n\u003e ***Notice that you will need ONLY ONE REDUCER per state!!!***\n So in the main AppState, you have 3 nested states, meaning that 3 reducers are optimal. But if you had AppState with no nested states, only with properties, like:   \n```  \nexport interface AppState {\n  counter: number;\n  username: string;\n  surname: string; \n  users: [];\n}\n```    \nin that case you will have only 1 reducer (check for ex. the users.reducer.ts).   \n\nIn **app.module.ts** add these:   \n```  \nStoreModule.forRoot({\n      userState: performLogin_Reducer,\n      counterState: counter_Reducer,\n    }),\n    \n// here it knows that this feature is actually for usersState, because when we were creating feature we provided that state interface  \nStoreModule.forFeature(usersFeatureKey, users_Reducer), \n\nEffectsModule.forRoot([UsersEffects]),\n```   \nThis userState, counterState ***MUST BE THE SAME NAME, AS WE HAVE SPECIFIED IN THE MAIN STATE***   \nOTHERWISE, WILL GET ERRORS like:  \n      // core.mjs:6484 ERROR TypeError: Cannot read properties of undefined (reading 'username') at index.ts: 99: 31    \n\n***If you want to do only 1 import in app module, then create this:***  \n```  \nin some file do this:   \nexport const main_Reducer: ActionReducerMap\u003cAppState\u003e = {\n  counterState: counter_Reducer,\n  userState: performLogin_Reducer,\n  usersState: users_Reducer\n};\n\nand in app.module.ts:  \nStoreModule.forRoot(main_Reducer)\n\n```    \nYou will not need feature for users.   \n\nFor **redux dev tools** chrome extension, import these lines in app.module.ts:  \n```  \nStoreDevtoolsModule.instrument({\n      maxAge: 25,\n      logOnly: environment.production,// Retains last 25 states\n      features: {\n        pause: true, // start/pause recording of dispatched actions\n        lock: true, // lock/unlock dispatching actions and side effects    \n        jump: true, // jump back and forth (time travelling)\n        skip: true, // skip (cancel) actions\n        reorder: true, // drag and drop actions in the history list \n        dispatch: true, // dispatch custom actions or action creators\n        test: true // generate tests for the selected actions\n      },\n    }),\n```   \n\u003e  ***IF WE PUT THIS BEFORE STORE MODULE REGISTRATIONS, THEN DEV TOOLS REDUX WILL NOT WORK!!!***   \n-----------------------------\n### How to dispatch actions and call selectors in the component?\nThe logic in **app.component.ts** contains code that:  \n- triggers action \n```\nthis.store.dispatch(increment_Action());\nthis.store.dispatch(loadAllUsers_Action());\n...\nthis.store.dispatch(loginUser_Action({ username: un, password: pw }));\n...\nthis.store.dispatch(deleteUserById_Action(user.id));\n```\n- **selectors**  \n  a) async pipe in HTML on the subject  \n```\nthis.count$ = this.store.select(getCount_Selector); // this is subject, we can use it in HTML such as\n// HTML file\n  \u003cb\u003e{{ count$ | async }}\u003c/b\u003e  \n \u003cb\u003e{{ (count$ | async)?.someProperty }}\u003c/b\u003e\n```  \nUsing **async pipe** on observable\u003cObject\u003e in HTML and **bind it to a local variable in HTML**  \n  \n```\n\u003cdiv *ngIf=\"user$ | async; let user\"\u003e\n  \u003ch3\u003e {{user.name}} \u003c/h3\u003e\n\u003c/div\u003e\n```  \n\n```\n\u003cdiv *ngIf=\"(user$ | async) || {}; let user\"\u003e\n  \u003ch3\u003e {{user?.name}} \u003c/h3\u003e\n\u003c/div\u003e\n```\nIf you contain *ngIf with more statements, you can omit that statement with ng-container, get a variable, and then use it below where needed (for more readability):  \n```\n \u003cng-container *ngIf=\"employeesPendingCounter$ | async;let counter;\"\u003e\n   \u003cspan *ngIf=\"selectedSubjectType === subjectTypeEnum.Employee \u0026\u0026 counter != 0\" fcSuffix class=\"badge\"\u003e\n     {{counter}}\n   \u003c/span\u003e\n \u003c/ng-container\u003e\n\n// or this works the same (less readable)\n \u003cspan *ngIf=\"selectedSubjectType === subjectTypeEnum.Employee \u0026\u0026 employeesPendingCounter$ | async;let counter; \" fcSuffix class=\"badge\"\u003e\n   {{counter}}\n \u003c/span\u003e\n```  \nb) using **subscribe** (with(out) pipe)  \n```\n// ts file\nthis.store.select(getAllUsers_Selector).subscribe((x) =\u003e console.log(x)); // this is subscription object, with real value\nthis.store.pipe(select(getCount_Selector)).subscribe((x) =\u003e console.log(x)); // same as previous, but a bit longer\n```\n\n\n*If we call selector -\u003e value is read from state, read-only  \nIf we call action -\u003e value is getting through reducer, depending on the reducer logic, it's a possible change of value in the state*\n\n-----------------------------\n### Effects\nFor the **effects**, we're getting the data from the online fake api:   \n```   \ngetUsers() {\n    // online json fake api \n    return this.http.get(`https://jsonplaceholder.typicode.com/users`);\n  }\n```     \nmake sure you added in app.module in imports:  \n```   \nEffectsModule.forRoot([UsersEffects]),\n```  \nEffect file:   \n\n \n```   \n @Effect()\n  loadUsers$ = this.actions$\n    .pipe(\n      ofType(loadAllUsers_Action),\n      mergeMap(() =\u003e this.apiService.getUsers()\n        .pipe(\n          map((allUsers): any =\u003e loadAllUsersSuccess_Action({ users: allUsers }),\n            catchError(error =\u003e EMPTY)),\n          // we could also create action for failure response\n            )\n        ));\n```\n#### Effect without dispatching action\nWe need to use  **{ dispatch: false }** \n```\ncompleteSignupSuccess$ = createEffect(\n  () =\u003e {\n    return this.actions$.pipe(\n      ofType(completeSignupSuccess),\n      tap(() =\u003e {\n        localStorage.removeItem('account');\n        localStorage.removeItem('emailToken');\n        this.router.navigate(['/account/login']);\n      })\n    );\n  },\n  { dispatch: false }\n);\n```\n#### Accessing action param in the effect\nIf your action looks like this (notice that parameter is called mediParam)\n```\n    export const getAvatars = createAction('[Dashboard API] Get Avatars',\n      props\u003c{mediParam: any;}\u003e()\n    );\n```  \nThen in effect, you will access it like this:  \n```\n action.mediParam\n```\nNgrx effect:  \n```\ngetAvatars1$ = createEffect(() =\u003e {\n    return this.actions$.pipe(\n      ofType(actions.getAvatars),\n      concatLatestFrom(() =\u003e [this.store.select(selectState)]),\n      switchMap(([action, state]) =\u003e {\n        let param = action.mediParam;\n        return this.bgcService.getAvatars(param).pipe(\n          map((data: any) =\u003e {\n            let avatars = JSON.parse(JSON.stringify(data.body));\n            return actions.getAvatarsSuccess({ data: avatars});\n          })\n        );\n      }))\n  });\n```\n#### Returning multiple actions from effect\nThe effect should **NOT** return multiple actions.  (https://github.com/timdeschryver/eslint-plugin-ngrx/blob/main/docs/rules/no-multiple-actions-in-effects.md)\n#### Call action from effect or reducer?\nYou should not do that, it's an **antipattern.**   \n- no-dispatch-in-effects - Effect should not call store.dispatch. (https://github.com/timdeschryver/eslint-plugin-ngrx/blob/main/docs/rules/no-dispatch-in-effects.md)\n   \nIf you dispatch some action from effect before you return ```success action```, then the state will not be yet updated, so if you try to gather state info, it will not have the latest data in it. The same for reducers, reducers should not have side effects.\n\nSo what you should do instead?  \nYou will make an effect, that will be listening to success action.    \nOnce the effect is done, it will trigger a success action, and then we listen for that action.  \n\u003e Take a look on **getDashboardWithEmployeesSuccess**, once the effect **getDashboardWithEmployees$** is done, it triggers success action **getDashboardWithEmployeesSuccess**, and then \nwe created effect **getAvatars$**, and listen for that action.  \n```\n getDashboardWithEmployees$ = createEffect(() =\u003e {\n   return this.actions$.pipe(\n     ofType(actions.getDashboardWithEmployees),\n     concatLatestFrom(() =\u003e [this.store.select(selectState)]),\n     switchMap(([action, state]) =\u003e {\n       return this.bgcService.getBackgroundCheckDashboard(SubjectTypeEnum.Employee).pipe(\n         map((data: any) =\u003e {\n           let dashboardData = JSON.parse(JSON.stringify(data.body));\n           this.addInitialsAndAvatarColors(dashboardData);\n           this.getColorForUserAvatar(dashboardData);\n           return actions.getDashboardWithEmployeesSuccess({ data: dashboardData });\n         })\n       );\n     })\n   );\n });\n\n  getAvatars$ = createEffect(() =\u003e {\n    return this.actions$.pipe(\n      ofType(actions.getDashboardWithEmployeesSuccess),\n      concatLatestFrom(() =\u003e [this.store.select(selectState)]),\n      switchMap(([action, state]) =\u003e {\n        let employeeIds: number[] = state.dashboardEmployees.map((bgc) =\u003e {\n          if (!!bgc.EmployeeID)\n            return bgc.EmployeeID\n        })\n        console.log(employeeIds)\n       return this.bgcService.getAvatars(employeeIds).pipe(\n          map((data: any) =\u003e {\n            let dataParsed = JSON.parse(JSON.stringify(data.body));\n            let dashboardData = JSON.parse(JSON.stringify(state.dashboardEmployees)); // this.store.select(selectDashboardEmployees);\n            dataParsed.forEach(employee =\u003e {\n              debugger\n              let existingUserIndex = dashboardData.findIndex(user =\u003e user.EmployeeID == employee.EmployeeID);\n              if (existingUserIndex != -1) {\n                dashboardData[existingUserIndex].Base64Avatar = 'data:image/png;base64, '+employee.Avatar;\n              }\n            })\n            return actions.getAvatarsSuccess({ data: dashboardData });\n          })\n        );\n      })\n    );\n  });\n```\n\n### How to make sure we trigger some code, when action is done?\nIf we have some **guard.ts**, and we want to check some value in the state (sapphireStatus), but probably **action is in progress**, and *we cannot know when it's done*.  \nSo in that case, we're setting sapphireStatus to some **initial value that cannot be returned by API**, in my case I set it as *null*. And then used the filter operator to filter all results if they are null.  \n\n\n**filter((sapphireStatus) =\u003e sapphireStatus != null)**\n```\ncanActivate(\n  route: ActivatedRouteSnapshot,\n  state: RouterStateSnapshot\n): Observable\u003cboolean | UrlTree\u003e | Promise\u003cboolean | UrlTree\u003e | boolean | UrlTree {\n  this.store.dispatch(getSapphireServices());\n  let path = route.url[0].path;\n  return this.store.select(selectSaphireStatus).pipe(\n    filter((sapphireStatus) =\u003e sapphireStatus != null), //This is part where we're filtering/ignoring the initial value\n    map((sapphireStatus) =\u003e {\n      switch (path) {\n        case 'dashboard':\n          if (sapphireStatus == SapphireStatusEnum.Active) return true;\n          return false;\n      }\n    })\n  );\n}\n```\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdacili%2Fngrx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdacili%2Fngrx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdacili%2Fngrx/lists"}