{"id":17109241,"url":"https://github.com/mrkpks/nx-optimistic-state","last_synced_at":"2026-01-08T04:55:02.344Z","repository":{"id":80611953,"uuid":"449763525","full_name":"mrkpks/nx-optimistic-state","owner":"mrkpks","description":"Optimistic state updates using @nrwl/angular#optimisticUpdate","archived":false,"fork":false,"pushed_at":"2022-02-09T15:29:28.000Z","size":709,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-04T18:50:47.058Z","etag":null,"topics":["angular","monorepo","ngrx","nrwl","nrwl-nx","typescript"],"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/mrkpks.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-01-19T16:12:00.000Z","updated_at":"2022-09-30T14:27:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"70b4375e-df7d-49f6-be8a-b4b566478eb7","html_url":"https://github.com/mrkpks/nx-optimistic-state","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/mrkpks%2Fnx-optimistic-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkpks%2Fnx-optimistic-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkpks%2Fnx-optimistic-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkpks%2Fnx-optimistic-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrkpks","download_url":"https://codeload.github.com/mrkpks/nx-optimistic-state/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246223267,"owners_count":20743158,"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","monorepo","ngrx","nrwl","nrwl-nx","typescript"],"created_at":"2024-10-14T16:22:34.061Z","updated_at":"2026-01-08T04:55:02.314Z","avatar_url":"https://github.com/mrkpks.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n# NxOptimisticState\n\n### Quickstart:\n\n`$ npm ci`\n\n`$ npx nx serve ou-ui`\n\nThis simple project was created as a PoC to show comparison of two different approaches to handling API calls using NgRx Store and @nrwl/angular.\n\n\n## Sources overview\ncheck these files for more info:\n\n- [tasks.actions.ts](https://github.com/mrkpks/nx-optimistic-state/blob/main/libs/tasks/src/lib/%2Bstate/tasks.actions.ts)\n- [tasks.reducer.ts](https://github.com/mrkpks/nx-optimistic-state/blob/main/libs/tasks/src/lib/%2Bstate/tasks.reducer.ts)\n- [tasks.effects.ts](https://github.com/mrkpks/nx-optimistic-state/blob/main/libs/tasks/src/lib/%2Bstate/tasks.effects.ts)\n\n## Classic approach\n\nUsing loading progress in views triggered by loading flags on FE.\n\nThis pattern is relying on 3 types of NgRx Store Actions:\n- *Trigger Action* (create, update/edit, delete) which triggers API call; e.g. `deleteTask`\n- *Success Action* which processes the response from API; e.g. `deleteTaskSuccess`\n- *Failure Action* which processes the error response from API;  e.g. `deleteTaskFailure`\n\nClassic success            |  Classic error\n:-------------------------:|:-------------------------:\n![image](https://user-images.githubusercontent.com/15088626/151183748-8239e693-1d33-464a-a625-e28be8cf5284.png) |  ![image](https://user-images.githubusercontent.com/15088626/151183879-f6a30e87-0318-40bf-84cf-b274c2ebcc07.png)\n\n### Examples\n\n#### Classic delete\n\n```ts\nimport * as TasksActions from './tasks.actions';\n\n...\n  deleteTask$ = createEffect(() =\u003e\n    this.actions$.pipe(\n      ofType(TasksActions.deleteTask),\n      fetch({\n        run: ({ id }) =\u003e\n          this.fakeAPI\n            .deleteTask(id)\n            .pipe(map((id) =\u003e TasksActions.deleteTaskSuccess({ id }))),\n        onError: (action, error) =\u003e {\n          console.error('Error deleteTask$: ', error);\n          this.message.error('Could not delete task');\n          return TasksActions.deleteTaskFailure({ error });\n        },\n      })\n    )\n  );\n```\n\nClassic success            |  Classic error\n:-------------------------:|:-------------------------:\n![delete-classic-scss](https://user-images.githubusercontent.com/15088626/151186828-6258a100-208d-4957-9b1e-0ae89bafa1a9.gif) |  ![delete-classic-err](https://user-images.githubusercontent.com/15088626/151186851-5b58aeda-106c-40bc-8eee-465cff39bdee.gif)\n\n#### Classic Create task implementation\n\n```ts\nimport * as TasksActions from './tasks.actions';\n\n...\n  createTask$ = createEffect(() =\u003e\n    this.actions$.pipe(\n      ofType(TasksActions.createTask),\n      fetch({\n        run: (action) =\u003e\n          this.fakeAPI\n            .createTask(action.name, action.status)\n            .pipe(map((task) =\u003e TasksActions.createTaskSuccess({ task }))),\n        onError: (action, error) =\u003e {\n          console.error('Error createTask$: ', error);\n          this.message.error('Could not create task');\n          return TasksActions.createTaskFailure({ error });\n        },\n      })\n    )\n  );\n```\n\nClassic success            |  Classic error\n:-------------------------:|:-------------------------:\n![clsc-scss](https://user-images.githubusercontent.com/15088626/151178163-51bf4811-8d7c-44b8-a393-e65380e77784.gif)  |  ![clsc-err](https://user-images.githubusercontent.com/15088626/151178180-747c46e7-36f9-42a8-8437-5af97919fa2f.gif)\n\n## Optimistic approach\n\nThe Optimistic UI is ideal when the responses from API take longer than desired from UX point of view.\nThis approach assumes a high success rate of received API responses and (in ideal case*) expecting the response from API wouldn't differ from the actual data modified on client.\n\nMost times is relying on 2 types of NgRx Store Actions:\n- *Trigger Action* (create, update/edit, delete) which triggers API call **AND immediately sets the new state** containing new data on client (in store); e.g. `deleteTaskOptimistic`  \n- *Undo Action* which is triggered upon receiving an error from API. Its purpose is to **revert the new state to original state** before the data modification.\n\nOptimistic success            |  Optimistic error\n:-------------------------:|:-------------------------:\n![image](https://user-images.githubusercontent.com/15088626/151184041-1e977d20-bc5a-4c9d-8860-59913482408c.png) |  ![image](https://user-images.githubusercontent.com/15088626/151184114-cc0e7437-f2a9-4f6f-be78-a163ff23dea9.png)\n\n### Examples\n\n#### Optimistic delete\n\n```ts\nimport * as TasksActions from './tasks.actions';\n\n...\n  deleteTaskOptimistic$ = createEffect(() =\u003e\n    this.actions$.pipe(\n      ofType(TasksActions.deleteTaskOptimistic),\n      optimisticUpdate({\n        run: ({ task }) =\u003e\n          this.fakeAPI.deleteTask(task.id).pipe(switchMap(() =\u003e EMPTY)),\n        undoAction: ({ task }, error) =\u003e {\n          console.error('Error deleteTask$: ', error);\n          this.message.error('Could not delete task');\n          return TasksActions.undoDeleteTask({\n            error,\n            task,\n          });\n        },\n      })\n    )\n  );\n```\n\nOptimistic success            |  Optimistic error\n:-------------------------:|:-------------------------:\n![delete-opt-scss](https://user-images.githubusercontent.com/15088626/151186939-8120d705-1375-43ac-9be6-f124b7720189.gif) |  ![delete-opt-err](https://user-images.githubusercontent.com/15088626/151186957-42401d51-96dc-4e31-ab27-e10b74b31010.gif)\n\n## Edge case for optimistic approach\n\nIn some cases, **just as in optimistic create**, there can be a requirement for optimistic approach even though the API response differs in some way from the data modified on client.\nThis use-case can be handled by adding a *Success Action* which re-maps or adds missing data to our entity.\n\nOptimistic success            |  Optimistic error\n:-------------------------:|:-------------------------:\n![image](https://user-images.githubusercontent.com/15088626/151184470-761b038c-ebcc-45df-851f-621fa0f6bff9.png) |  ![image](https://user-images.githubusercontent.com/15088626/151184553-32181893-5514-464c-930c-f531d38ecc29.png)\n\n### Examples\n\n#### Optimistic Create task implementation\n\nIn this case we don't know what ID will be generated on BE, so we need to generate our own on client and replace it in Success Action\n\n```ts\nimport { optimisticUpdate } from '@nrwl/angular';\n\nimport * as TasksActions from './tasks.actions';\n\n...\n  createTaskOptimistic$ = createEffect(() =\u003e\n    this.actions$.pipe(\n      ofType(TasksActions.createTaskOptimistic),\n      optimisticUpdate({\n        run: (action) =\u003e\n          this.api.createTask(action.task.name, action.task.status).pipe(\n            tap(() =\u003e this.message.success('Task created')),\n            // needs another action for replacing Optimistic ID (oid)\n            map((task) =\u003e\n              TasksActions.createTaskOptimisticSuccess({\n                oid: action.task.id,\n                task,\n              })\n            )\n          ),\n        undoAction: (action, error) =\u003e {\n          // add error handling here\n          console.error('Error createTask$');\n          // call Undo Action\n          return TasksActions.undoCreateTask({\n            error,\n            /**\n            * notice the the Undo Action's payload - in this case ID is enough, \n            * we're just removing it from the TasksEntities\n            **/\n            id: action.task.id, \n          });\n        },\n      })\n    )\n  );\n```\n\nOptimistic success            |  Optimistic error\n:-------------------------:|:-------------------------:\n![optmstc-scss](https://user-images.githubusercontent.com/15088626/151178105-3b0df0e3-937f-4fe4-bd7a-98c216f81fbe.gif)  |  ![optmstc-err](https://user-images.githubusercontent.com/15088626/151178130-da73f3b0-a492-4811-8773-594f6cf3a28b.gif)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrkpks%2Fnx-optimistic-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrkpks%2Fnx-optimistic-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrkpks%2Fnx-optimistic-state/lists"}