{"id":51820504,"url":"https://github.com/aliomnt/angular-fetcher","last_synced_at":"2026-07-24T14:00:36.734Z","repository":{"id":301035656,"uuid":"1007950562","full_name":"aliomnt/angular-fetcher","owner":"aliomnt","description":"Signal-Based State Management for Remote API Data","archived":false,"fork":false,"pushed_at":"2026-07-03T14:33:20.000Z","size":130,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-03T16:24:58.940Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/aliomnt.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-24T19:37:01.000Z","updated_at":"2026-07-03T14:33:25.000Z","dependencies_parsed_at":"2025-06-24T21:26:40.827Z","dependency_job_id":"46b31de1-f735-43f8-9e78-af39063cb404","html_url":"https://github.com/aliomnt/angular-fetcher","commit_stats":null,"previous_names":["aliomnt/angular-fetcher"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/aliomnt/angular-fetcher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliomnt%2Fangular-fetcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliomnt%2Fangular-fetcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliomnt%2Fangular-fetcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliomnt%2Fangular-fetcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aliomnt","download_url":"https://codeload.github.com/aliomnt/angular-fetcher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aliomnt%2Fangular-fetcher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35844578,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-24T02:00:07.870Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2026-07-22T05:00:25.531Z","updated_at":"2026-07-24T14:00:36.720Z","avatar_url":"https://github.com/aliomnt.png","language":"TypeScript","funding_links":[],"categories":["Architecture and Advanced Topics"],"sub_categories":["HTTP"],"readme":"# Angular Fetcher\n\n#### Signal-Based State Management for Remote API Data\n\nangular-fetcher is a modern, signal-based library for managing remote API data in Angular applications. It offers a clean, reactive way to handle data fetching, mutations, and error tracking, all while keeping your code organized and type-safe. Built to work seamlessly with Angular's ecosystem, it empowers you to create responsive, robust apps with minimal effort. for managing remote API data in Angular applications. It offers a clean, reactive way to handle data fetching, mutations, and error tracking, all while keeping your code organized and type-safe. Built to work seamlessly with Angular's ecosystem, it empowers you to create responsive, robust apps with minimal effort.\n\n## Table of Contents\n\n- [Why Angular Fetcher?](#why-angular-fetcher)\n- [Features](#features)\n- [Angular Compatibility](#angular-compatibility)\n- [Installation](#installation)\n- [Fetching Data](#fetching-data)\n- [Handling Mutations](#handling-mutations)\n- [Empty Value Option](#empty-value-option)\n- [Invalidate vs. Fetch](#invalidate-vs-fetch)\n- [Abort Requests](#abort-requests)\n- [Http Interceptor](#http-interceptor)\n- [License](#license)\n\n\u003cbr /\u003e\n\n## Why Angular Fetcher?\n\nangular-fetcher makes working with remote APIs in Angular a breeze. By leveraging Angular Signals and RxJS, it provides a powerful, type-safe API to manage data, loading states, and errors reactively. Whether you're fetching user lists or updating records with optimistic updates, this library ensures your app stays performant and maintainable, all with a syntax that feels natural in Angular.\n\n\u003cbr /\u003e\n\n## Features\n\n- ✅ **Reactive Signals**: Track data, loading states, and errors in real-time using Angular Signals.\n- ✅ **Data Fetching**: Load or refresh API data with full control over existing state.\n- ✅ **Mutations**: Perform updates with optimistic changes and track specific mutation states using mutationLoadingKey.\n- ✅ **Type Safety**: Enjoy TypeScript support for better code reliability and IDE assistance.\n- ✅ **Angular Integration**: Works smoothly with Angular's dependency injection and RxJS.\n\n\u003cbr /\u003e\n\n## Angular Compatibility\n\n| Angular Version | Supported Package Version |\n| --------------- | ------------------------- |\n| Angular `22.x`  | `^22.0.0`                 |\n| Angular `21.x`  | `^21.0.0`                 |\n| Angular `20.x`  | `^20.1.1`                 |\n| Angular `19.x`  | `^19.0.2`                 |\n| Angular `18.x`  | `^18.0.1`                 |\n| Angular `17.x`  | `^17.0.1`                 |\n\n\u003e Please install the latest patch and minor version of each major release to ensure optimal compatibility.\n\n\u003cbr /\u003e\n\n## Installation\n\nAdd angular-fetcher to your project:\n\n```bash\nnpm install angular-fetcher\n```\n\n\u003cbr /\u003e\n\n## Fetching Data\n\nCreate a resource in your service to fetch data from an API using withResource. This sets up a reactive state for data, loading, and errors.\n\n```ts\nimport { Injectable } from \"@angular/core\";\nimport { HttpClient } from \"@angular/common/http\";\nimport { withResource } from \"angular-fetcher\";\nimport { Observable } from \"rxjs\";\n\ninterface User {\n  id: number;\n  name: string;\n}\n\n@Injectable({ providedIn: \"root\" })\nexport class UserService {\n  private http = inject(HttpClient);\n\n  usersResource = withResource(() =\u003e this.getUsers());\n\n  private getUsers(): Observable\u003cUser[]\u003e {\n    return this.http.get\u003cUser[]\u003e(\"/api/users\");\n  }\n}\n```\n\nUse the resource in a component to display data and handle refreshes. The `invalidate()` method clears the current data and fetches fresh results.\n\n```ts\nimport { Component } from \"@angular/core\";\nimport { UserService } from \"./user.service\";\n\n@Component({\n  selector: \"app-user-list\",\n  template: `\n    \u003cbutton (click)=\"reload()\"\u003eReload Users\u003c/button\u003e\n\n    @if (users.state.fetchLoading()) {\n      \u003cdiv\u003eLoading users...\u003c/div\u003e\n    } @else if (users.state.error()) {\n      \u003cdiv class=\"error\"\u003e{{ users.state.error()?.message }}\u003c/div\u003e\n    } @else {\n      \u003cdiv class=\"user-list\"\u003e\n        @for (user of users.state.data(); track user.id) {\n          \u003cdiv\u003e{{ user.name }}\u003c/div\u003e\n        }\n      \u003c/div\u003e\n    }\n  `,\n  styles: [\n    `\n      .user-list {\n        margin-top: 16px;\n      }\n      .error {\n        color: red;\n      }\n    `,\n  ],\n})\nexport class UserListComponent {\n  userService = inject(UserService);\n  users = this.userService.usersResource;\n\n  ngOnInit() {\n    this.users.fetch(); // Load data initially\n  }\n\n  reload() {\n    this.users.invalidate(); // Clear and refetch data\n  }\n}\n```\n\nYou can also use the optional `handlers` argument with `fetch()` to respond to success or error states programmatically:\n\n```ts\nthis.users.fetch({\n  next: (res) =\u003e this.showToast(\"Users loaded: \", res),\n  error: (err) =\u003e this.showToast(`Failed to load users: ${err.message}`),\n});\n```\n\n\u003cbr /\u003e\n\n## Handling Mutations\n\nUse `withMutation` to perform operations like adding a user, with support for optimistic updates and tracking specific mutation states via `mutationLoadingKey`.\n\n```ts\nimport { Injectable } from \"@angular/core\";\nimport { HttpClient } from \"@angular/common/http\";\nimport { withResource } from \"angular-fetcher\";\nimport { Observable } from \"rxjs\";\n\ninterface User {\n  id: number;\n  name: string;\n}\n\n@Injectable({ providedIn: \"root\" })\nexport class UserService {\n  private http = inject(HttpClient);\n\n  usersResource = withResource(() =\u003e this.getUsers());\n\n  private getUsers(): Observable\u003cUser[]\u003e {\n    return this.http.get\u003cUser[]\u003e(\"/api/users\");\n  }\n\n  addUser(user: User): Observable\u003cUser\u003e {\n    return this.http.post\u003cUser\u003e(\"/api/users\", user);\n  }\n}\n```\n\nIn a component, trigger the mutation and show a success message using a `showToast` method.\n\n```ts\nimport { Component } from \"@angular/core\";\nimport { UserService } from \"./user.service\";\n\n@Component({\n  selector: \"app-add-user\",\n  template: `\n    \u003cinput [(ngModel)]=\"newUser.name\" placeholder=\"Enter name\" /\u003e\n    \u003cbutton\n      (click)=\"addUser()\"\n      [disabled]=\"users.state.mutationLoadingKey()['add-user']\"\n    \u003e\n      {{\n        users.state.mutationLoadingKey()[\"add-user\"] ? \"Adding...\" : \"Add User\"\n      }}\n    \u003c/button\u003e\n  `,\n})\nexport class AddUserComponent {\n  userService = inject(UserService);\n  users = this.userService.usersResource;\n  newUser: User = { id: 0, name: \"\" };\n\n  addUser() {\n    this.users.withMutation(this.userService.addUser(this.newUser), {\n      key: \"add-user\",\n      optimisticUpdate: (prev) =\u003e [...prev, this.newUser],\n      invalidate: true, // Refetch users after mutation\n      next: () =\u003e this.showToast(\"User added successfully!\"),\n      error: (err) =\u003e this.showToast(`Error: ${err.message}`),\n    });\n    this.newUser = { id: 0, name: \"\" };\n  }\n\n  private showToast(message: string) {\n    console.log(message); // Replace with your toast service\n  }\n}\n```\n\n\u003e Optimistic Update Explanation: Optimistic update is a technique where changes are applied immediately in the UI before receiving confirmation from the server. This improves perceived responsiveness by instantly reflecting user actions. If the server confirms the change, the update remains; if the request fails, the UI reverts to the previous state to maintain consistency.\n\n\u003cbr/\u003e\n\nThe `mutationLoadingKey()['add-user']` tracks the mutation's loading state, and `optimisticUpdate` instantly updates the UI while `invalidate: true` ensures the data syncs with the server.\n\n\u003cbr /\u003e\n\n## Empty Value Option\n\nYou can set an `emptyValue` in `withResource` to define the initial or reset state of the data. For example:\n\n```ts\nusersResource = withResource(() =\u003e this.getUsers(), {\n  emptyValue: [] as User[],\n});\n```\n\nThis ensures the data starts as an empty array until fetched.\n\n\u003cbr /\u003e\n\n## Invalidate vs. Fetch\n\n- `fetch()`: Loads data while keeping the current data until the new response arrives. Perfect for initial loads or refreshing without clearing the UI.\n- `invalidate()`: Resets the data to its `emptyValue` (or empty object if not set) and fetches fresh results. Ideal for ensuring the latest server state, like after a mutation.\n\n\u003cbr /\u003e\n\n## Abort Requests\n\nBy default, if multiple requests are made to the same resource and a previous request has not yet completed, Angular Fetcher will automatically abort the previous request and only proceed with the latest one. This ensures your data is always fresh and consistent with the most recent interaction.\n\nYou can also abort a request manually using the `abort()` method if it hasn't completed yet:\n\n```ts\nthis.usersResource.abort();\n```\n\nThis is useful for cancelling background requests when navigating away from a view, or to reset state deliberately.\n\n\u003cbr /\u003e\n\n## Http Interceptor\n\nBecause angular-fetcher uses Angular’s HttpClient internally, it fully supports all features of Angular’s HTTP client, including interceptors. This means you can take advantage of authentication tokens, logging, error handling, and any custom request modifications seamlessly within angular-fetcher without additional setup.\n\n\u003cbr/\u003e\n\n## License\n\nMIT License | Ali Montazeri\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faliomnt%2Fangular-fetcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faliomnt%2Fangular-fetcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faliomnt%2Fangular-fetcher/lists"}