{"id":26643688,"url":"https://github.com/kosich/rxjs-autorun","last_synced_at":"2025-04-10T23:51:20.008Z","repository":{"id":41447505,"uuid":"298813086","full_name":"kosich/rxjs-autorun","owner":"kosich","description":"Re-evaluate an expression whenever Observable in it emits","archived":false,"fork":false,"pushed_at":"2020-11-11T19:11:52.000Z","size":394,"stargazers_count":35,"open_issues_count":7,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-24T20:38:44.807Z","etag":null,"topics":["angular","frp","js","reactive","reactive-programming","rxjs","ts"],"latest_commit_sha":null,"homepage":"","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/kosich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-09-26T12:49:46.000Z","updated_at":"2024-10-31T05:59:12.000Z","dependencies_parsed_at":"2022-08-25T13:30:59.523Z","dependency_job_id":null,"html_url":"https://github.com/kosich/rxjs-autorun","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kosich%2Frxjs-autorun","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kosich%2Frxjs-autorun/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kosich%2Frxjs-autorun/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kosich%2Frxjs-autorun/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kosich","download_url":"https://codeload.github.com/kosich/rxjs-autorun/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248317707,"owners_count":21083528,"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","frp","js","reactive","reactive-programming","rxjs","ts"],"created_at":"2025-03-24T20:34:40.307Z","updated_at":"2025-04-10T23:51:19.987Z","avatar_url":"https://github.com/kosich.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003e\n    \u003cbr/\u003e\n    🧙‍♂️ RxJS️ Autorun 🧙‍♀️\n    \u003cbr/\u003e\n    \u003cbr/\u003e\n    \u003cimg src=\"https://dev-to-uploads.s3.amazonaws.com/i/509pq2z20ea3hn4d3ug5.png\" width=\"358px\" /\u003e\n    \u003cbr/\u003e\n    \u003csub\u003e\u003csub\u003eEvaluates given expression whenever dependant Observables emit\u003c/sub\u003e\u003c/sub\u003e\n    \u003cbr/\u003e\n    \u003cbr/\u003e\n    \u003ca href=\"https://www.npmjs.com/package/rxjs-autorun\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/rxjs-autorun\" alt=\"NPM\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://bundlephobia.com/result?p=rxjs-autorun@latest\"\u003e\u003cimg src=\"https://img.shields.io/bundlephobia/minzip/rxjs-autorun?label=gzipped\" alt=\"Bundlephobia\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://opensource.org/licenses/MIT\" rel=\"nofollow\"\u003e\u003cimg src=\"https://img.shields.io/npm/l/rxjs-autorun\" alt=\"MIT license\"\u003e\u003c/a\u003e\n  \u003c/h1\u003e\n\u003c/div\u003e\n\n## 📦 Install\n\n```\nnpm i rxjs-autorun\n```\n\nOr **[try it online](https://stackblitz.com/edit/rxjs-autorun-repl?file=index.ts)**\n\n**⚠️ WARNING:** at this stage it's a very experimental library, use at your own risk!\n\n## 💃 Examples\n\n### Instant evaluation:\n\n```ts\nconst o = of(1);\nconst r = combined(() =\u003e $(o));\nr.subscribe(console.log); // \u003e 1\n```\n\n### Delayed evaluation:\n\n_`combined` waits for Observable `o` to emit a value_\n\n```ts\nconst o = new Subject();\nconst r = combined(() =\u003e $(o));\nr.subscribe(console.log);\no.next('🐈'); // \u003e 🐈\n```\n\n### Two Observables:\n\n_recompute `c` with latest `a` and `b`, only when `b` updates_\n\n```ts\nconst a = new BehaviorSubject('#');\nconst b = new BehaviorSubject(1);\nconst c = combined(() =\u003e _(a) + $(b));\n\nc.subscribe(observer); // \u003e #1\na.next('💡'); // ~no update~\nb.next(42); // \u003e 💡42\n```\n\n### Filtering:\n\n_use [NEVER](https://rxjs.dev/api/index/const/NEVER) to suspend emission till `source$` emits again_\n\n```ts\nconst source$ = timer(0, 1_000);\nconst even$ = combined(() =\u003e $(source$) % 2 == 0 ? _(source$) : _(NEVER));\n```\n\n### Switchmap:\n\n_fetch data every second_\n\n```ts\nfunction fetch(x){\n  // mock delayed fetching of x\n  return of('📦' + x).pipe(delay(100));\n}\n\nconst a = timer(0, 1_000);\nconst b = combined(() =\u003e fetch($(a)));\nconst c = combined(() =\u003e $($(b)));\nc.subscribe(console.log);\n// \u003e 📦 1\n// \u003e 📦 2\n// \u003e 📦 3\n// \u003e …\n```\n\n\n## 🔧 API\n\nTo run an expression, you must wrap it in one of these:\n\n- `combined` returns an Observable that will emit evaluation results\n\n- `computed` returns an Observable that will emit **distinct** evaluation results with **distinctive updates**\n\n- `autorun` internally subscribes to `combined` and returns the subscription\n\nE.g:\n\n```ts\ncombined(() =\u003e { … });\n```\n\n### 👓 Tracking\n\nYou can read values from Observables inside `combined` (or `computed`, or `autorun`) in two ways:\n\n- `$(O)` tells `combined` that it should be re-evaluated when `O` emits, with it's latest value\n\n- `_(O)` still provides latest value to `combined`, but doesn't enforce re-evaluation with `O` emission\n\nBoth functions would interrupt mid-flight if `O` has not emitted before and doesn't produce a value synchronously.\n\nIf you don't want interruptions — try Observables that always contain a value, such as `BehaviorSubject`s, `of`, `startWith`, etc.\n\nUsually this is all one needs when to use `rxjs-autorun`\n\n### 💪 Strength\n\nSome times you need to tweak what to do with **subscription of an Observable that is not currently used**.\n\nSo we provide three levels of subscription strength:\n\n- `normal` - default - will unsubscribe if the latest run of expression didn't use this Observable:\n\n  ```ts\n  combined(() =\u003e $(a) ? $(b) : 0)\n  ```\n\n  when `a` is falsy — `b` is not used and will be **dropped when expression finishes**\n\n  _NOTE: when you use `$(…)` — it applies normal strength, but you can be explicit about that via `$.normal(…)` notation_\n\n\n- `strong` - will keep the subscription for the life of the expression:\n\n  ```ts\n  combined(() =\u003e $(a) ? $.strong(b) : 0)\n  ```\n\n  when `a` is falsy — `b` is not used, but the subscription will be **kept**\n\n\n- `weak` - will unsubscribe eagerly, if waiting for other Observable to emit:\n\n  ```ts\n  combined(() =\u003e $(a) ? $.weak(b) : $.weak(c));\n  ```\n\n  When `a` is truthy — `c` is not used and we'll wait `b` to emit,\n  meanwhile `c` will be unsubscribed eagerly, even before `b` emits\n\n  And vice versa:\n  When `a` is falsy — `b` is not used and we'll wait `c` to emit,\n  meanwhile `b` will be unsubscribed eagerly, even before `c` emits\n\n  Another example:\n\n  ```ts\n  combined(() =\u003e $(a) ? $(b) + $.weak(c) : $.weak(c))\n  ```\n\n  When `a` is falsy — `b` is not used and will be dropped, `c` is used\n  When `a` becomes truthy - `b` and `c` are used\n  Although `c` will now have to wait for `b` to emit, which takes indefinite time\n  And that's when we might want to mark `c` for **eager unsubscription**, until `a` or `b` emits\n\n\nSee examples for more use-case details\n\n## ⚠️ Precautions\n\n### Sub-functions\n\n`$` and `_` memorize Observables that you pass to them. That is done to keep subscriptions and values and not to re-subscribe to same `$(O)` on each re-run.\n\nTherefore if you create a new Observable on each run of the expression:\n\n```ts\nlet a = timer(0, 100);\nlet b = timer(0, 1000);\nlet c = combined(() =\u003e $(a) + $(fetch($(b))));\n\nfunction fetch(): Observable\u003cany\u003e {\n  return ajax.getJSON('…');\n}\n```\n\nIt might lead to unexpected fetches with each `a` emission!\n\nIf that's not what we need — we can go two ways:\n\n- create a separate `combined()` that will call `fetch` only when `b` changes — see [switchMap](#switchmap) example for details\n\n- use some memoization or caching technique on `fetch` function that would return same Observable, when called with same arguments\n\n### Side-effects\n\nIf an Observable doesn't emit a synchronous value when it is subscribed, the expression will be **interrupted mid-flight** until the Observable emits.\nSo if you must make side-effects inside `combined` — put that after reading from streams:\n\n```ts\nconst o = new Subject();\ncombined(() =\u003e {\n  console.log('Hello'); // DANGEROUS: perform a side-effect before reading from stream\n  return $(o);          // will fail here since o has not emitted yet\n}).subscribe(console.log);\no.next('World');\n\n/** OUTPUT:\n * \u003e Hello\n * \u003e Hello\n * \u003e World\n */\n```\n\n While:\n\n```ts\nconst o = new Subject();\ncombined(() =\u003e {\n  let value = $(o); // will fail here since o has not emitted yet\n  console.log('Hello'); // SAFE: perform a side-effect after reading from stream\n  return value;\n}).subscribe(console.log);\no.next('World');\n\n/** OUTPUT:\n * \u003e Hello\n * \u003e World\n */\n```\n\n*We might introduce [alternative APIs](https://github.com/kosich/rxjs-autorun/issues/3) to help with this*\n\n### Logic branching\n\nLogic branches might lead to late subscription to a given Observable, because it was not seen on previous runs. And if your Observable doesn't produce a value synchronously when subscribed — then expression will be **interrupted mid-flight** until any visited Observable from this latest run emits a new value.\n\n*We might introduce [alternative APIs](https://github.com/kosich/rxjs-autorun/issues/3) to help with this*\n\nAlso note that you might want different handling of unused subscriptions, please see [strength](#-strength) section for details.\n\n### Synchronous values skipping\n\nCurrently `rxjs-autorun` will skip synchronous emissions and run expression only with latest value emitted, e.g.:\n\n```ts\nconst o = of('a', 'b', 'c');\n\ncombined(() =\u003e $(o)).subscribe(console.log);\n\n/** OUTPUT:\n * \u003e c\n */\n```\n\n*This might be fixed in future updates*\n\n## 🤝 Want to contribute to this project?\n\nThat will be awesome!\n\nPlease create an issue before submitting a PR — we'll be able to discuss it first!\n\nThanks!\n\n## Enjoy 🙂\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkosich%2Frxjs-autorun","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkosich%2Frxjs-autorun","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkosich%2Frxjs-autorun/lists"}