{"id":13627783,"url":"https://github.com/ngneat/effects","last_synced_at":"2025-04-07T15:09:23.945Z","repository":{"id":39604296,"uuid":"384338585","full_name":"ngneat/effects","owner":"ngneat","description":"🪄 A framework-agnostic RxJS effects implementation","archived":false,"fork":false,"pushed_at":"2024-07-04T07:06:37.000Z","size":21848,"stargazers_count":63,"open_issues_count":10,"forks_count":11,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-29T22:44:55.377Z","etag":null,"topics":["angular","angular-effects","effects","effects-functions","react","react-effects","rxjs"],"latest_commit_sha":null,"homepage":"https://www.netbasal.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ngneat.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.config.js","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","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},"funding":{"github":"ngneat"}},"created_at":"2021-07-09T06:07:21.000Z","updated_at":"2024-08-16T06:05:04.000Z","dependencies_parsed_at":"2024-11-06T23:30:38.974Z","dependency_job_id":"2f17e08b-adac-4016-b453-d2d7d48b9497","html_url":"https://github.com/ngneat/effects","commit_stats":{"total_commits":127,"total_committers":13,"mean_commits":9.76923076923077,"dds":0.7952755905511811,"last_synced_commit":"c24f562e4240977e34ec154d63e10b6c17e16d4e"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Feffects","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Feffects/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Feffects/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Feffects/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ngneat","download_url":"https://codeload.github.com/ngneat/effects/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244826306,"owners_count":20516734,"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","angular-effects","effects","effects-functions","react","react-effects","rxjs"],"created_at":"2024-08-01T22:00:38.444Z","updated_at":"2025-03-24T10:09:34.030Z","avatar_url":"https://github.com/ngneat.png","language":"TypeScript","readme":"\u003cp align=\"center\"\u003e\n \u003cimg width=\"15%\" height=\"15%\" src=\"effects.png\"\u003e\n\u003c/p\u003e\n\n\u003cbr /\u003e\n\n\u003e A framework-agnostic RxJS effects implementation\n\n\n[![@ngneat/effects](https://github.com/ngneat/effects/actions/workflows/ci.yml/badge.svg)](https://github.com/ngneat/effects/actions/workflows/ci.yml)\n![commitizen](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)\n![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)\n![coc-badge](https://img.shields.io/badge/codeof-conduct-ff69b4.svg?style=flat-square)\n![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e5079.svg?style=flat-square]https://github.com/semantic-release/semantic-release)\n![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square]https://github.com/prettier/prettier)\n\n👉 Play with the code on [stackblitz](https://stackblitz.com/edit/react-ts-phemyx?devtoolsheight=50\u0026file=index.tsx)\n\n# Effects\n\nFirst, we need to initialize the the library by calling the `initEffects()` function:\n\n```ts\nimport { initEffects } from '@ngneat/effects';\n\ninitEffects();\n```\n\nActions are created by using the `createAction` or `actionsFactory` functions:\n\n```ts\nimport { actionsFactory, createAction, props } from '@ngneat/effects';\n\n// todos.actions.ts\nexport interface Todo {\n  id: string;\n  name: string;\n}\n\nexport const addTodo = createAction('[Todos] Add Todo', props\u003c{ title: string }\u003e());\n\n// We recommend using the actions factory to prefix each action \n// for better readability and debug purposes when using redux dev tools\nexport const todoActions = actionsFactory('todo');\n\n// We can declare an action by passing it a type and an optional payload. \nexport const loadTodos = todoActions.create('Load Todos')\nexport const addTodo   = todoActions.create('Add Todo', props\u003cTodo\u003e())\n```\n\nNext, we need to define the `effects`, and register them:\n\n```ts\nimport { createEffect, registerEffects, ofType, tapResult } from '@ngneat/effects';\n\nexport const addTodo$ = createEffect((actions) =\u003e\n    actions.pipe(\n      ofType(addTodo),\n      switchMap(() =\u003e apiCall().pipe(\n        tapResult(console.log, console.error)\n      ))\n    );\n  )\n);\n\nregisterEffects([addTodo$])\n```\n\nThe `tapResult` operator safely handles the result. It enforces that the effect would still be running in case of error. Finally, we can dispatch actions using the `dispatch` function:\n\n```ts\nimport { dispatch } from '@ngneat/effects';\n\ndispatch(addTodo({ title: 'effects' }));\n```\n`tapResult` also let us specify a custom error and completed handler. If no custom error handling is specified, a possible error will be printed to the console.\n\n## Use with React\n\nFirst, install the package: `npm i @ngneat/effects-hook`.\n\nNow, we can use the `useEffects` hook and pass our effects:\n\n```ts\nimport { useEffects } from '@ngneat/effects-hook';\nimport { dispatch }   from '@ngneat/effects';\nimport { useEffect }  from 'react';\n\nexport function TodosPage() {\n  useEffects([loadTodos$, addTodo$]);\n\n  useEffect(() =\u003e dispatch(loadTodos()), []);\n\n  return (\n    \u003cbutton onClick = {() =\u003e dispatch(addTodo({ title: 'foo' }))}\u003e\n      Add\n    \u003c/button\u003e\n  )\n}\n```\n\nThe effects we pass are tied to the component life cycle hook and will be destroyed with the component.\n\n## Use with Angular\n\nFirst, install the package: `npm i @ngneat/effects-ng`.\n\nNext, create the `effect` provider:\n\n```ts\nimport { createEffect } from '@ngneat/effects';\n\n@Injectable({ providedIn: 'root' })\nexport class TodosEffects {\n\n  constructor(private todosApi: TodosApi) {}\n\n  loadTodos$ = createEffect(actions =\u003e actions.pipe(\n    ofType(loadTodos),\n    switchMap((todo) =\u003e this.todosApi.loadTodos())\n  ));\n}\n```\n\nBy default, the return value of an effect doesn't dispatch an action. You can get this behavior by passing the { dispatch: false } option as a second parameter.\n\nThen we need to register `effects manager` by calling `provideEffectsManager` at the root level. Also to register effects at the root level we need to call `provideEffect` function:\n\n```ts\nimport { provideEffectsManager, provideEffect } from '@ngneat/effects-ng';\nimport { TodosEffects } from 'todos/todos.effect.ts';\n\n@NgModule({\n  providers: [\n    /**\n     *  provideEffectsManager({ dispatchByDefault: true }),\n     */\n    provideEffectsManager(),\n    provideEffect(TodosEffects),\n  ]\n})\nexport class AppModule {\n}\n\n-- OR --\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    /**\n     *  provideEffectsManager({ dispatchByDefault: true }),\n     */\n    provideEffectsManager(),\n    provideEffects(TodosEffects)\n  ],\n});\n```\nThe `provideEffectsManager` function can take the global configuration. \nWe can set the `dispatchByDefault` property to true for each effect to dispatch the resulting action. The default is set to false.\n\nAs stated above, this behavior can be overwritten on each effect.\n\nIn order to register lazily loaded effects use the `provideEffect` function on the [`envirenment`](https://angular.io/api/core/EnvironmentInjector) that you need:\n\n```ts\nimport { provideEffect } from '@ngneat/effects-ng';\nimport { PostsEffects } from \"posts/posts.effect.ts\"\n\n@NgModule({\n  providers: [\n    provideEffect(PostsEffects),\n  ]\n})\nexport class LazyModule {\n}\n\n-- OR --\n\nexport ROUTES = [\n  ...,\n  {\n    path: 'lazy',\n    loadChildren: () =\u003e import('./lazy-route/lazy.routes').then(mod =\u003e mod.ROUTES),\n    providers: [provideEffect(PostsEffects)],\n  }\n]\n```\n\nThe actions can be dispatched by injecting the `Actions` provider:\n\n```ts\nimport { Actions } from '@ngneat/effects-ng';\n\n@Component(...)\nexport class AppComponent {\n  constructor(private actions: Actions) {}\n\n  ngOnInit() {\n    this.actions.dispatch(loadTodos());\n  }\n}\n```\n\n\u003e Registering an effects class multiple times, either by `forRoot()`, `forFeature()`, or `provideEffects()`, (for example in different lazy loaded features) will not cause the effects to run multiple times.\n\n#### Directive Effects\n`provideDirectiveEffects()` and `EffectsDirective` serve to register effects on the `component injector` level. This means that effects will live as long as the component where effects are registered lives. Do not forget to call `provideEffectsManager` in the root providers.\n\n```ts\nimport { provideDirectiveEffects, EffectsDirective, Actions } from '@ngneat/effects-ng';\n\n@Component({\n  ...,\n  providers: [provideDirectiveEffects(TodosEffects)],\n  hostDirectives: [EffectsDirective],\n})\nexport class TodosComponent {\n  constructor(private actions: Actions) {}\n\n  ngOnInit() {\n    this.actions.dispatch(loadTodos());\n  }\n}\n```\n\n\u003e If multiple components register the same effects via `provideDirectiveEffects() \u0026 EffectsDirective` it will not cause the effects to run multiple times. The effects will be running until the last component that registered these effects via `provideDirectiveEffects() \u0026 EffectsDirective` is destroyed. \nIf the same effects were registered multiple times via `forRoot(), forFeature(), provideEffects()` and `provideDirectiveEffects() \u0026 EffectsDirective` then after the component is destroyed the effects will be still running.\n\n### Testing\n\nIn order to test effect classes and using the actions stream from parameter you can substitute the action stream by a\ncustom created action stream. It's recommended to only use this feature for testing purposes.\n\n```ts\ndescribe(\"Effect test\", () =\u003e {\n  // use a custom action stream to replace the stream before each test\n  let customActionsStream: Actions;\n\n  beforeEach(() =\u003e {\n    customActionsStream = new Actions();\n\n    TestBed.configureTestingModule({\n      providers: [\n        provideEffectsManager({ customActionsStream }),\n        provideEffect(EffectsOne),\n      ]\n    });\n  })\n})\n```\n\n# Effect Functions\n\nTo use an `effect` function we first need to create it by using the `createEffectFn` function:\n\n```ts\nimport { createEffectFn } from '@ngneat/effects';\n\nexport const searchTodoEffect = createEffectFn((searchTerm$: Observable\u003cstring\u003e) =\u003e {\n  return searchTerm$.pipe(\n    debounceTime(300),\n    switchMap((searchTerm) =\u003e fetchTodos({ searchTerm })),\n  );\n});\n```\n\nThe `createEffectFn` function takes a `callback` function which is passed an `Observable` parameter and returns\nan `Observable`.\n\n### Use with React\n\nFirst, install the package: `npm i @ngneat/effects-hook`.\n\nWe can register the effect in our component, and call it when we need:\n\n```ts\nimport { useEffectFn } from '@ngneat/effects-hooks';\n\nfunction SearchComponent() {\n  const searchTodo = useEffectFn(searchTodoEffect);\n\n  return \u003cinput onChange = {({ target: { value } }) =\u003e searchTodo(value) }/\u003e\n}\n```\n\nEvery time the `effect` is called, its value is pushed into that `Observable`.\n\nWe can also register multiple effects:\n\n```ts\nfunction FooComponent() {\n  const [addTodo, updateTodo, deleteTodo] = useEffectFn([\n    addTodoEffect, updateTodoEffect, deleteTodoEffect\n  ]);\n\n  return ...\n}\n```\n\n\u003ch3 id=\"angular-effect-functions\"\u003eUse with Angular\u003c/h3\u003e\n\nFirst, install the package: `npm i @ngneat/effects-ng`.\n\nCreate an effect class, extends the `EffectFn` class and use the `createEffectFn` method to create your effects:\n\n```ts\nimport { EffectFn } from '@ngneat/effects-ng';\n\nexport class TodosEffects extends EffectFn {\n\n  searchTodo = this.createEffectFn((searchTerm$: Observable\u003cstring\u003e) =\u003e \n    searchTerm$.pipe(\n      debounceTime(300),\n      switchMap((searchTerm) =\u003e fetchTodos({ searchTerm })),\n    );\n  );\n}\n```\n\nInject the effects provider in your component, and call it when you need:\n\n```ts\n@Component({\n  providers: [TodosEffects],\n})\nexport class TodosComponent {\n  constructor(private todosEffects: TodosEffects) {\n  }\n\n  ngOnInit() {\n    this.control.valueChanges.subscribe(searchTerm =\u003e {\n      this.todosEffects.searchTodo(searchTerm);\n    });\n  }\n}\n```\n\n\u003cdiv\u003eIcons made by \u003ca href=\"https://www.freepik.com\" title=\"Freepik\"\u003eFreepik\u003c/a\u003e from \u003ca href=\"https://www.flaticon.com/\" title=\"Flaticon\"\u003ewww.flaticon.com\u003c/a\u003e\u003c/div\u003e\n","funding_links":["https://github.com/sponsors/ngneat"],"categories":["Projects by main language","Underlying Technologies"],"sub_categories":["typescript","RxJS"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngneat%2Feffects","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fngneat%2Feffects","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngneat%2Feffects/lists"}