{"id":13511398,"url":"https://github.com/sebholstein/tinystate","last_synced_at":"2025-03-20T04:50:03.937Z","repository":{"id":57166701,"uuid":"123805603","full_name":"sebholstein/tinystate","owner":"sebholstein","description":"A tiny, yet powerful state management library for Angular","archived":false,"fork":false,"pushed_at":"2019-01-20T09:09:27.000Z","size":1194,"stargazers_count":224,"open_issues_count":3,"forks_count":15,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-05-17T06:11:02.962Z","etag":null,"topics":["angular","flux","ngrx","ngx","redux","state-management"],"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/sebholstein.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":"2018-03-04T16:12:03.000Z","updated_at":"2024-04-30T12:42:38.000Z","dependencies_parsed_at":"2022-08-30T15:21:40.817Z","dependency_job_id":null,"html_url":"https://github.com/sebholstein/tinystate","commit_stats":null,"previous_names":["sebastianm/tinystate"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebholstein%2Ftinystate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebholstein%2Ftinystate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebholstein%2Ftinystate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sebholstein%2Ftinystate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sebholstein","download_url":"https://codeload.github.com/sebholstein/tinystate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244554132,"owners_count":20471173,"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","flux","ngrx","ngx","redux","state-management"],"created_at":"2024-08-01T03:00:49.127Z","updated_at":"2025-03-20T04:50:03.907Z","avatar_url":"https://github.com/sebholstein.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/tinystate-logo.png\" alt=\"TinyState\" /\u003e\u003cbr /\u003e\n  A tiny, yet powerful state management library for Angular inspired by \u003ca href=\"https://github.com/jamiebuilds/unstated\"\u003eUnstated\u003c/a\u003e.\u003cbr /\u003e\n  Created by \u003ca href=\"https://twitter.com/Sebholstein\"\u003e@Sebholstein\u003c/a\u003e\n\u003c/div\u003e\n\n---\n\n[![Build Status](https://travis-ci.org/SebastianM/tinystate.svg?branch=master)](https://travis-ci.org/SebastianM/tinystate) [![codecov](https://codecov.io/gh/SebastianM/tinystate/branch/master/graph/badge.svg)](https://codecov.io/gh/SebastianM/tinystate) [![npm version](https://badge.fury.io/js/%40tinystate%2Fcore.svg)](https://www.npmjs.com/package/@tinystate/core) ![Supported Angular versions: 6+](https://img.shields.io/badge/Supported%20Angular%20versions-6+-green.svg) ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)\n\n---\n\n## Introduction\n\nState management in every component-based application is hard. TinyState tries to solve the following problem:  \n\n\u003e Sharing state between components as simple as possible and leverage the good parts of component state and Angular`s dependency injection system.\n\n## Demo\n\nDemo on [Stackblitz.io](https://stackblitz.com/edit/tinystate-demo)\n\n## Installation\n\n```bash\nyarn add @tinystate/core\n# or\nnpm install @tinystate/core\n```\n\n### Loading the module in the app/root module\n\n```typescript\nimport { TinyStateModule } from '@tinystate/core';\n\n@NgModule({\n  imports: [\n    CommonModule,\n    TinyStateModule.forRoot()\n  ]\n})\nclass AppModule {}\n```\n\n## Example\n\n```typescript\nimport { Container } from '@tinystate/core';\n\nexport interface CounterState {\n  count: number;\n}\n\n/**\n * A Container is a very simple class that holds your state and some logic for updating it.\n * The shape of the state is described via an interface (in this example: CounterState).\n */\nexport class CounterContainer extends Container\u003cCounterState\u003e {\n  getInitialState(): CounterState {\n    return {\n      count: 0\n    };\n  }\n\n  increment(increment: number) {\n    this.setState(state =\u003e ({ count: state.count + increment }));\n  }\n\n  decrement(decrement: number = 1) {\n    this.setState(state =\u003e ({ count: state.count - decrement }));\n  }\n}\n\n@Component({\n  selector: 'my-component',\n  template: `\n    \u003ch1\u003e\n      Counter: {{ counter$ | async }}\n    \u003c/h1\u003e\n    \u003cbutton (click)=\"increment()\"\u003eIncrement\u003c/button\u003e\n    \u003cbutton (click)=\"decrement()\"\u003eDecrement\u003c/button\u003e\n  `,\n  providers: [\n    CounterContainer\n  ]\n})\nexport class MyComponent {\n  counter$: Observable\u003cnumber\u003e = this.counterContainer.select(state =\u003e state.count);\n\n  constructor(private counterContainer: CounterContainer) {}\n\n  increment() {\n    this.counterContainer.increment(1);\n  }\n\n  decrement() {\n    this.counterContainer.decrement();\n  }\n}\n```\n\n## Global state\n\nThe example shown above creates a `CounterContainer` instance for the `MyComponent` and is also injectable for all **child** components of the `MyComponent`.\n\nIf you have global state that should be injectable in all your components, add the `providedIn: 'root'` option to the `@Injectable` decorator of the Container:\n\n```typescript\n@Injectable({\n  providedIn: 'root'\n})\nclass CounterContainer {}\n```\n\nWith the configuration show above, you can inject the `CounterContainer` container in every component of your application.\n\n## Testing containers\n\nTesting containers is really easy. Let's say we want to write a test for the following container:\n\n```typescript\nimport { Container } from '@tinystate/core';\n\nexport interface CounterState {\n  count: number;\n}\n\nexport class CounterContainer extends Container\u003cCounterState\u003e {\n  getInitialState(): CounterState {\n    return {\n      count: 0\n    };\n  }\n\n  increment() {\n    this.setState(state =\u003e ({ count: state.count + 1 }));\n  }\n}\n```\n\nHere's an example of a possible test with Jasmine:\n\n```typescript\nimport { CounterContainer } from './counter.container';\nimport { TestBed, inject } from '@angular/core/testing';\n\ndescribe('CounterContainer', () =\u003e {\n  beforeEach(() =\u003e {\n    TestBed.configureTestingModule({\n      providers: [CounterContainer]\n    });\n  });\n\n  it(\n    'should have an initial count state of 0',\n    inject([CounterContainer], (container: CounterContainer) =\u003e {\n      let count: number | undefined;\n      container.select(s =\u003e s.count).subscribe(s =\u003e (count = s));\n      expect(count).toEqual(0);\n    })\n  );\n\n  it(\n    'should increment the count by one when calling increment',\n    inject([CounterContainer], (container: CounterContainer) =\u003e {\n      let count: number | undefined;\n      container.select(s =\u003e s.count).subscribe(s =\u003e (count = s));\n      container.increment();\n      expect(count).toEqual(1);\n    })\n  );\n});\n```\n\n## Testing components that use containers\n\nLet's write a test for the following component:\n\n```typescript\n@Component({\n  selector: 'my-component',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\n    \u003cdiv class=\"count\"\u003e{{ count$ | async }}\u003c/div\u003e\n    \u003cbutton class=\"inc-count\" (click)=\"increment()\"\u003einc\u003c/button\u003e\n  `\n})\nclass MyComponent {\n  count$: Observable\u003cnumber\u003e = this.counterContainer.select(s =\u003e s.count);\n\n  constructor(private counterContainer: CounterContainer) {}\n\n  increment() {\n    this.counterContainer.increment();\n  }\n}\n```\n\n```typescript\ndescribe('MyComponent', () =\u003e {\n  let counterContainer: CounterContainer;\n\n  beforeEach(\n    async(() =\u003e {\n      counterContainer = jasmine.createSpyObj\u003cCounterContainer\u003e('CounterContainer', [\n        'increment',\n        'select'\n      ]);\n      return TestBed.configureTestingModule({\n        declarations: [MyComponent],\n        providers: [{ provide: CounterContainer, useValue: counterContainer }]\n      }).compileComponents();\n    })\n  );\n\n  it('should increment via the counter', () =\u003e {\n    const fixture = TestBed.createComponent(MyComponent);\n    fixture.debugElement.query(By.css('.inc-count')).triggerEventHandler('click', null);\n    expect(counterContainer.increment).toHaveBeenCalledTimes(1);\n  });\n});\n```\n\n## Redux Devtools Support\n\nTo enable support for the Redux Devtools Extension, add the following module to your root NgModule:\n\n```typescript\nimport { TinyStateModule, ReduxDevtoolsPluginModule } from '@tinystate/core';\n\n@NgModule({\n  imports: [\n    CommonModule,\n    TinyStateModule.forRoot(),\n    ReduxDevtoolsPluginModule.forRoot()\n  ],\n  providers: [\n    CounterContainer\n  ]\n})\nclass AppModule {}\n```\n\nTinyState hasn't the concept of Actions. So the action name will always be `NO_NAME`. But you will see how the state of all your containers change, which is even without action names useful for debugging:\n\n![Redux Devtools Demo Gif](docs/assets/devtools-demo.gif)\n\n## FAQ\n\n### When should I use TinyState?\n\nDo you have a global state or a state that is needed in several components that you want to share between them and think that solutions like NGRX or Redux are a way too heavy for your simple use case? - then TinyState could be for you.\n\nTinyState is not a solution that should be seen as an alternative to NGRX or Redux because these projects are trying to solve different problems than TinyState wants to solve.\n\nIMO local component state is totally fine as long as it works for you. So choose the right tool for the right job.\n\n### Can I use the action/reducer pattern with TinyState?\n\nNope. The goal of this project is to keep sharing state between components simple. If you think your state is too complex/big or you want a replayable, fully predictable state container, you should consider using [NGRX](https://github.com/ngrx/platform), [NGXS](https://github.com/amcdnl/ngxs) or [Redux](https://github.com/reactjs/redux).\n\n### What are the differences between TinyState and [Unstated](https://github.com/jamiebuilds/unstated)?\n\n* Unstated supports React - TinyState supports Angular.\n* TinyState uses RxJS as the base for all the state handling whereas Unstated uses plain objects. RxJS plays very well together with Angular and allows powerful streaming transformations.\n* Unstated uses the React Context API and a self-implemented Injection pattern whereas TinyState uses Angular's built-in [ Hierarchical Dependency Injectors](https://angular.io/guide/hierarchical-dependency-injection) to create/assign Container instances to component hierarchies.\n* TinyState supports Redux Devtools and has a plugin API.\n","funding_links":[],"categories":["angular","TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsebholstein%2Ftinystate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsebholstein%2Ftinystate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsebholstein%2Ftinystate/lists"}