{"id":26897759,"url":"https://github.com/sascha245/angular-hooks","last_synced_at":"2025-05-13T00:33:22.248Z","repository":{"id":57178531,"uuid":"200190083","full_name":"sascha245/angular-hooks","owner":"sascha245","description":"Use Vue function api in Angular","archived":false,"fork":false,"pushed_at":"2019-08-07T07:33:51.000Z","size":28,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-02T06:20:13.956Z","etag":null,"topics":["angular","function-api","hooks"],"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/sascha245.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":"2019-08-02T07:46:05.000Z","updated_at":"2022-05-07T19:41:47.000Z","dependencies_parsed_at":"2022-09-09T19:00:15.352Z","dependency_job_id":null,"html_url":"https://github.com/sascha245/angular-hooks","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/sascha245%2Fangular-hooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sascha245%2Fangular-hooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sascha245%2Fangular-hooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sascha245%2Fangular-hooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sascha245","download_url":"https://codeload.github.com/sascha245/angular-hooks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253850279,"owners_count":21973661,"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","function-api","hooks"],"created_at":"2025-04-01T05:06:51.146Z","updated_at":"2025-05-13T00:33:22.214Z","avatar_url":"https://github.com/sascha245.png","language":"TypeScript","readme":"# angular-hooks\n\nUse [Vue Function API](https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md) equivalent in Angular to enable composition functions in components and providers:\n- Easier synchroneous observables, including computed values with automatic dependency detection.\n- Dynamic lifecycle hooks\n- Automatic observable unsubscription with `subscribe` and `watch`.\n- Better logical decomposition and code reusability\n\n**Warning**: This is currently still experimental and unstable.\n\n## TODO\n\n- `state` function \u0026 unwrapping of wrappers in template\n\n- add options `watch` to choose update mode:\n  - `sync`: call watch handler synchroneously when a dependency has changed.\n  - `pre`: call watch handler before rerendering\n  - `post`: call watch handler after rerendering\n\n- rename `this.$data`?\n\n## Install\n\n1. Make sure Angular v6 or higher is installed.\n2. Make sure RxJs v6 or higher is installed.\n\n3. Install module:\n`npm install angular-hooks --save`\n\n## Usage\n\nTo use Angular Hooks, you need to first let your component inherit `UseHooks\u003cT\u003e` with `T` being your component. This allows us to add the necessary logic and typing to the component before it is executed.\n\nTo finish, you only need to add the `ngHooks` method to your component. The return value of this function will automatically be exposed on `this.$data`.\n\n## Restrictions\n\n- `ngHooks` needs to be synchroneous.\n- All functions presented below are only available in `ngHooks`.\n\n## Features\n\nFor now, there is no advanced description available for each function.\nEach function therefore only links to an example using the feature.\n\nObservables wrappers:\n- [value](#example-setup)\n- [observe](#example-inputs)\n- [computed](#example-inputs)\n- [watch](#example-route)\n\nAutomatic subscribe / unsuscribe:\n- subscribe\n\nInjector:\n- [provide](#example-route)\n\nDynamic lifecycles:\n- [onInit](#example-lifecycles)\n- [onDestroy](#example-lifecycles)\n- onAfterViewInit\n- onAfterContentInit\n\n## Example\n\n### \u003ca name=\"example-setup\"\u003e\u003c/a\u003e Setup\n\n- `value` returns a new Wrapper with the given initial value.\n\n```ts\nimport { value, UseHooks } from 'angular-hooks'\n\n@Component({\n  // ...\n})\nexport class MyComponent extends UseHooks\u003cMyComponent\u003e {\n\n  ngHooks() {\n    const counter = value(0);\n\n    return {\n      counter\n    };\n  }\n}\n```\n\n```html\n\u003cp\u003e{{ $data.counter.value }}\u003c/p\u003e\n```\n\n### \u003ca name=\"example-inputs\"\u003e\u003c/a\u003e Reactive inputs\n\n- `observe` turns a property on the given object into a reactive Wrapper.\n- `computed` automatically recomputes it's value if one of it's dependencies has changed. It will also only recompute it's value when needed.\n\n```ts\nimport { observe, computed, UseHooks } from 'angular-hooks'\n\n@Component({\n  // ...\n})\nexport class MyComponent extends UseHooks\u003cMyComponent\u003e {\n  @Input()\n  title: string = \"Hello world\";\n\n  ngHooks() {\n    const title = observe(this, props =\u003e props.title);\n    const reversedTitle = computed(() =\u003e {\n      return title.value\n        .split('')\n        .reverse()\n        .join('');\n    })\n\n    return {\n      title,\n      reversedTitle\n    };\n  }\n}\n```\n\n```html\n\u003ch1\u003e{{ $data.title.value }}\u003c/h1\u003e\n\u003ch2\u003e{{ $data.reversedTitle.value }}\u003c/h2\u003e\n```\n\n### \u003ca name=\"example-route\"\u003e\u003c/a\u003e Watch route\n\n- `provide` makes use of Angulars `Injector` to get the appropriate provider.\n- `fromObservable` turns an RxJs observable into a Wrapper.\n- `asObservable` turns a Wrapper into an RxJs observable.\n- `watch` observes a Wrapper and triggers the handler each time the Wrapper changes. Automatically unsubscribes when the component is destroyed.\n\n```ts\nimport { value, provide, watch, fromObservable, UseHooks } from 'angular-hooks'\nimport { ActivatedRoute } from '@angular/router';\n\nfunction useRoute() {\n  const route = provide(ActivatedRoute);\n  const params = fromObservable(router.params);\n  return {\n    params\n  }\n}\n\n@Component({\n  // ...\n})\nexport class MyComponent extends UseHooks\u003cMyComponent\u003e {\n\n  ngHooks() {\n    const route = useRoute();\n    const id = computed(() =\u003e route.params.value.id);\n\n    const todo = value\u003cany\u003e(undefined);\n\n    watch(id, async (value) =\u003e {\n      const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${value}`);\n      todo.value = await res.json();\n    })\n\n    return {\n      id,\n      todo\n    };\n  }\n}\n```\n\n```html\n\u003ch1\u003e{{ $data.id.value }}\u003c/h1\u003e\n\u003cdiv *ngIf=\"$data.todo.value\"\u003e\n  \u003cp\u003e{{ $data.todo.value.title }}\u003c/p\u003e\n\u003c/div\u003e\n```\n\n### \u003ca name=\"example-lifecycles\"\u003e\u003c/a\u003e Dynamic lifecycle hooks\n\n```ts\nimport { onInit, onDestroy, value } from 'angular-hooks'\n\nfunction useMouse() {\n  const x = value(0);\n  const y = value(0);\n\n  const update = (e: MouseEvent) =\u003e {\n    x.value = e.pageX;\n    y.value = e.pageY;\n  };\n\n  onInit(() =\u003e {\n    window.addEventListener(\"mousemove\", update, false);\n  });\n  onDestroy(() =\u003e {\n    window.removeEventListener(\"mousemove\", update, false);\n  });\n\n  return {\n    x,\n    y\n  };\n}\n\n@Component({\n  // ...\n})\nexport class MyComponent extends UseHooks\u003cMyComponent\u003e {\n\n  ngHooks() {\n    return {\n      ...useMouse()\n    };\n  }\n}\n```\n\n```html\n\u003cp\u003e{{ $data.x.value }} - {{ $data.y.value }}\u003c/p\u003e\n```\n\n### useAsync\n\nFirst create our `useAsync` function. This function will return an observable `data` and `error` object, as well as an `execute` function to launch the asynchroneous operation.\n\n```ts\nfunction useAsync\u003cT = any\u003e(fn: () =\u003e Promise\u003cT\u003e) {\n  const loading = value\u003cboolean\u003e(false);\n  const data = value\u003cT|undefined\u003e(undefined);\n  const error = value\u003cany|undefined\u003e(undefined);\n  \n  const execute = async () =\u003e {\n    error.value = undefined;\n    loading.value = true;\n    try {\n      data.value = await fn();\n    } catch (err) {\n      error.value = err;\n    }\n    loading.value = false;\n  }\n  \n  return {\n    loading,\n    data,\n    error,\n    execute\n  }\n}\n```\n\nYou can then reuse it as you like in your components.\n```ts\n@Component({\n  // ...\n})\nexport class MyComponent extends UseHooks\u003cMyComponent\u003e {\n\n  ngHooks() {\n    const route = useRoute();\n    const id = computed(() =\u003e route.params.value.id);\n  \n    const myService = provide(MyService);\n    const { data: findData, error: findError, execute: fetchData } = useAsync(() =\u003e myService.find());\n    const { error: editError, execute: editData } = useAsync(() =\u003e myService.edit(id.value));\n    \n    // findError is an observable wrapper, so you can use it with computed or watch.\n    const findErrorCode = computed(() =\u003e findError.value.code);\n    \n    onInit(async () =\u003e {\n      await fetchData();\n    })\n    \n    return {\n      findData,\n      findErrorCode,\n      fetchData,\n      \n      editError,\n      editData\n    };\n  }\n}\n```\n\nAdvantages:\n- Better visibility about what happens in your component\n- `useAsync` is completely reusable\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsascha245%2Fangular-hooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsascha245%2Fangular-hooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsascha245%2Fangular-hooks/lists"}