{"id":22645503,"url":"https://github.com/akmjenkins/ngx-stateful","last_synced_at":"2026-05-04T02:32:13.288Z","repository":{"id":42817946,"uuid":"268161821","full_name":"akmjenkins/ngx-stateful","owner":"akmjenkins","description":"A StatefulComponent for Angular","archived":false,"fork":false,"pushed_at":"2023-01-06T07:28:14.000Z","size":1108,"stargazers_count":0,"open_issues_count":6,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-21T23:47:14.994Z","etag":null,"topics":["angular","component","ngx-stateful","state","state-management","stateful","stateful-components","stateless-co"],"latest_commit_sha":null,"homepage":null,"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/akmjenkins.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}},"created_at":"2020-05-30T21:22:28.000Z","updated_at":"2021-08-20T18:43:59.000Z","dependencies_parsed_at":"2023-02-05T15:16:49.031Z","dependency_job_id":null,"html_url":"https://github.com/akmjenkins/ngx-stateful","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/akmjenkins/ngx-stateful","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akmjenkins%2Fngx-stateful","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akmjenkins%2Fngx-stateful/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akmjenkins%2Fngx-stateful/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akmjenkins%2Fngx-stateful/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akmjenkins","download_url":"https://codeload.github.com/akmjenkins/ngx-stateful/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akmjenkins%2Fngx-stateful/sbom","scorecard":{"id":176364,"data":{"date":"2025-08-11","repo":{"name":"github.com/akmjenkins/ngx-stateful","commit":"bd98272455e63d725c8b7361963cebd366495c8f"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.3,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/12 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 12 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"23 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-16T17:45:37.656Z","repository_id":42817946,"created_at":"2025-08-16T17:45:37.657Z","updated_at":"2025-08-16T17:45:37.657Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32592398,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"online","status_checked_at":"2026-05-04T02:00:06.625Z","response_time":58,"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":["angular","component","ngx-stateful","state","state-management","stateful","stateful-components","stateless-co"],"created_at":"2024-12-09T06:06:08.260Z","updated_at":"2026-05-04T02:32:13.268Z","avatar_url":"https://github.com/akmjenkins.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ngx-stateful\n\n[![npm version](https://img.shields.io/npm/v/ngx-stateful.svg)](https://npmjs.org/package/ngx-stateful)\n[![Coverage Status](https://coveralls.io/repos/github/akmjenkins/ngx-stateful/badge.svg)](https://coveralls.io/github/akmjenkins/ngx-stateful)\n[![Build Status](https://travis-ci.com/akmjenkins/ngx-stateful.svg)](https://travis-ci.com/akmjenkins/ngx-stateful)\n[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/ngx-stateful)](https://bundlephobia.com/result?p=ngx-stateful)\n\n## Sample App \n\n[Simple Stackblitz App](https://stackblitz.com/edit/angular-ivy-k47m7k) (it's not a \"ToDo\") to show you how **ngx-stateful** works.\n\n## Why Stateful?\n\nFirst of all, you probably don't need this package, it's completely trivial to implement yourself using a [StateSubject](https://www.bennadel.com/blog/3522-creating-a-simple-setstate-store-using-an-rxjs-behaviorsubject-in-angular-6-1-10.htm). It looks like [ngrx has tried to do this](https://ngrx.io/guide/component-store) but they've failed to [KISS](https://en.wikipedia.org/wiki/KISS_principle). Plus (unpopular opinion ahead) dependency injection in Angular is not the way to go. Not because DI is inherently bad, but because most developers just don't use it for it's intended purpose. Instead of relying on abstract dependencies and \"programming to the interface\", it's used simply to share state around various services in the application in weird ways that are hard to debug.\n\nSecondly, just because your application uses state (spoiler alert: every application has state), doesn't mean it needs all the boilerplate of a [redux implementation](https://ngrx.io/guide/store).\n\nAfter working on an (unncessary) large scale angular application with way too many components with devs who had a variety of skill levels, I realized how unclear the concept of `State` is to a lot of developers, even though they may be using redux to manage their state.\n\nI blame OOP principles (yeah, I said it). When components are backed by instances of classes, often developers tend to put instance variables on their components without fully realizing that, by doing so, **they are causing that component to become a StatefulComponent** (see [StatefulWidget](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) if you know anything about [Flutter](https://api.flutter.dev/index.html)). \n\nAside - this is also the reason why I'm such a big fan of [React](https://reactjs.org) moving to functional components - not because react is better, but just because it tries to force developers to critically think about state before using it (typing [`useState`](https://reactjs.org/docs/hooks-reference.html#usestate) comes with more forethought than `this.someString = 'fred'`, IMO.)\n\nBack to Angular components with instance variables - these components then **own something**, and when state is scattered up and down your component tree (without it being clearly identifiable - does this component class have instance variables? How are they used?) then it just becomes a nightmare to onboard yourself into a large codebase.\n\nSo let's start making it obnoxiusly clear to all of your team members when a component in Angular owns state - it must extend `StatefulComponent\u003cT\u003e`, and whenever something about that state changes, it must call `setState`.\n\nIt provides some neat helper methods that make it easy to discern when and why state is being updated without looking through your component file for `this.someProp = X` and then checking your template file to see if `someProp` is being bound to something in the template.\n\n## Aren't you just trying to make Angular like React?\n\n**YES**... kind of, but that's because it **is** like React. React, Vue, Angular, and Flutter are all effectively the same - they render a tree of components, some of which own state (instance members? computed properties?) and the tree needs to update (render) when things change. All of these frameworks can be written in the exact same style, and they probably should be, it makes moving between applications written in in various frameworks tremendously easy. Unidirectional data flow and the composable component pattern of building user interface applications have easily won the day, so we, as developers, should embrace it fully rather than trying to invent new state management patterns each time a new framework shows up - looking at you [BLOC](https://medium.com/@aaron.chu/flutter-state-management-bloc-pattern-9cd6011c699).\n\n\n## How to use\n\n```js\nimport { StatefulComponent } from 'ngx-stateful';\n\ninterface MyState {\n    fetching: boolean;\n    data?: string[];\n    error: false;\n}\n\n@Component({\n    selector:'app',\n    template:`\n        \u003cng-container *ngIf=\"fetching$ | async; then spinner; else result\"\u003e\u003c/ng-container\u003e\n\n        \u003cng-template #result\u003e\n            \u003cng-container *ngIf=\"error$ | async; then error; else data\"\u003e\u003c/ng-container\u003e\n        \u003c/ng-template\u003e\n\n        \u003cng-template #spinner\u003e\n            \u003cmat-progress-spinner\u003e\u003c/mat-progress-spinner\u003e\n        \u003c/ng-template\u003e\n\n        \u003cng-template #error\u003e\n            \u003capp-error-component [error]=\"error$ | async\"\u003e\u003c/app-error-component\u003e\n        \u003c/ng-template\u003e\n\n        \u003cng-template #data\u003e\n            \u003capp-my-data-displayer [data]=\"data$ | async\"\u003e\u003c/app-my-data-displayer\u003e\n        \u003c/ng-template\u003e    \n    `\n})\nexport class MyComponent extends StatefulComponent\u003cMyState\u003e implements ngOnInit {\n\n    public fetching$ = this.selectKey\u003cMyState['fetching']\u003e('fetching');\n    public error$ = this.selectKey\u003cMyState['error']\u003e('error');\n    public data$ = this.selectKey\u003cMyState['data']\u003e('data');\n\n    constructor() {\n        super({\n            fetching:false,\n            error:false\n        })\n    }\n\n\n    async ngOnInit() {\n        this.setState({fetching:true});\n\n        try {\n            this.setState({data:await fetchSomeData()});\n        } catch(err) {\n            this.stateState({error:true});\n        }\n\n        this.setState({fetching:false});\n    }\n\n}\n```\n\n## API\n\nAny component class that extends this must call `super()` with the state:\n\n```js\nclass MyComponent extends StatefulComponent\u003cMyState\u003e {\n    constructor() {\n        super({...your state})\n    }\n}\n```\n\u003ca name=\"state\"\u003e\u003c/a\u003e\n- `public state: T`\n\nThe current **IMMUTABLE** state of the component.\n\n\u003ca name=\"state$\"\u003e\u003c/a\u003e\n- `public state$: Observable\u003cT\u003e`\n\nAn observable of your components state, think [async pipe](https://angular.io/api/common/AsyncPipe) in your template.\n\n\u003ca name=\"setstate\"\u003e\u003c/a\u003e\n- `public setState(state: StateSetter\u003cT\u003e)`\n\nSets the state of the component by either patching it or using the function signature.\n\n```js\nprivate startFetching() {\n    // patch function signature\n    this.setState({fetching:true});\n}\n\nprivate stopFetching() {\n    // callback function signature\n    this.setState(state =\u003e ({...state,fetching:false}));\n}\n```\n\n\u003ca name=\"select\"\u003e\u003c/a\u003e\n- `public select\u003cR\u003e(selector: (state: T) =\u003e R): Observable\u003cR\u003e`\n\nRetrieve state with a selector (see [reselect](https://github.com/reduxjs/reselect)).\n\n```js\npublic fetching$ = this.select(state =\u003e state,({fetching}) =\u003e fetching);\n```\n\u003ca name=\"selectkey\"\u003e\u003c/a\u003e\n- `public selectKey\u003cR\u003e(key: string): Observable\u003cR\u003e`\n\nRetrieve state by string key (when your state is an object).\n```js\npublic fetching$ = this.selectKey\u003cboolean\u003e('fetching');\n```\n\n\u003ca name=\"onchange\"\u003e\u003c/a\u003e\n- `*public onChange\u003cR\u003e(selector: (state: T) =\u003e R): Observable\u003c[R,R]\u003e`\n\nReturns on observable that emits only when a certain piece of the state changes.\n\n\u003ca name=\"onkeychange\"\u003e\u003c/a\u003e\n- `*public onKeyChange\u003cR\u003e(key: string): Observable\u003c[R,R]\u003e`\n\nSame deal as [selectKey](#selectkey)\n\n\n*Note: `onChange` and `onKeyChange` **DO NOT** emit if the piece of state being selected has never changed. See [pairwise](https://www.learnrxjs.io/learn-rxjs/operators/combination/pairwise).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakmjenkins%2Fngx-stateful","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakmjenkins%2Fngx-stateful","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakmjenkins%2Fngx-stateful/lists"}