{"id":29040458,"url":"https://github.com/0x1000000/ngsetstate","last_synced_at":"2025-08-07T20:22:12.757Z","repository":{"id":65457939,"uuid":"179245415","full_name":"0x1000000/ngSetState","owner":"0x1000000","description":"A library that helps developing UI (e.g. Angular or React) components in a more functional style where UI logic is representing as a series of immutable state transitions.","archived":false,"fork":false,"pushed_at":"2023-03-23T23:10:28.000Z","size":1081,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-06-23T01:39:52.852Z","etag":null,"topics":["angular","react","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/0x1000000.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-04-03T08:29:21.000Z","updated_at":"2024-05-06T20:29:09.000Z","dependencies_parsed_at":"2023-02-15T17:20:44.609Z","dependency_job_id":null,"html_url":"https://github.com/0x1000000/ngSetState","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/0x1000000/ngSetState","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FngSetState","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FngSetState/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FngSetState/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FngSetState/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0x1000000","download_url":"https://codeload.github.com/0x1000000/ngSetState/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FngSetState/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262081117,"owners_count":23255662,"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","react","state-management"],"created_at":"2025-06-26T14:05:36.785Z","updated_at":"2025-06-26T14:06:09.843Z","avatar_url":"https://github.com/0x1000000.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ngSetState\n\n## About\n\nA library that helps developing UI (e.g. Angular or React) components in a more functional style where UI logic is representing as a series of immutable state transitions.\n\n* [Tutorial](https://itnext.io/angular-components-state-tracking-with-ng-set-state-e2b988540407?source=friends_link\u0026sk=9a3596275dc73f72882fe2ec519b4528)\n* [React Demo (stackblitz.com)](https://stackblitz.com/edit/react-ts-yz6kuo?file=AppStateTrack.ts)\n* [Angular Demo (stackblitz.com)](https://stackblitz.com/edit/set-state-greet)\n* **Demo Application** - you can find a realistic usage of the library in this ASP.Net demo application - [SqGoods](https://github.com/0x1000000/SqGoods)\n\n## Table of Contents\n\n1. [Get Started](#get_satrted)\n2. [Glossary](#glossary)\n3. [API](#api)\n   - [Mapped Types](#api)\n   - [Initialization](#initialization)\n   - [Decorators](#decorators)\n      - [@With](#with)\n      - [@WithAsync](#with_async)\n      - [@WithAction](#with_action)\n      - [@WithActionAsync](#with_action_async)\n      - [@WithSharedAsSource](#with_shared_as_source)\n      - [@WithSharedAsTarget](#with_shared_as_target)\n      - [@AsyncInit](#async_init)\n      - [@Emitter](#emitter)\n      - [@BindToShared](#bind_to_shared)\n      - [@IncludeInState](#include_in_state)\n3. [Examples](#examples)\n   - [Shared Component](#examples)\n   - [Async](#async)\n   - [Actions](#actions)\n   - [Observables](#observables)\n   - [Using in React](#using-in-react)\n\n\u003ca name=\"get_satrted\"/\u003e\n\n## 1. Get Started\n\n**Step 1:** Install ng-set-state\n\n```sh\nnpm install ng-set-state\n```\n\n**Step 2:** Create a component with state tracking:\n\n```ts\nclass Component {\n    constructor() {\n        initializeImmediateStateTracking(this);\n    }\n\n    arg1: number = 0;\n\n    arg2: number = 0;\n\n    readonly sum: number = 0;\n\n    readonly resultString: string = '';\n\n    @With('arg1', 'arg2')\n    static calcSum(state: ComponentState\u003cComponent\u003e): StateDiff\u003cComponent\u003e {\n        return {\n            sum: state.arg1 + state.arg2\n        };\n    }\n\n    @With('sum')\n    static prepareResultString(state: ComponentState\u003cComponent\u003e): StateDiff\u003cComponent\u003e {\n        return {\n            resultString: `Result: ${state.sum}`\n        };\n    }    \n}\n\nconst component = new Component();\n\ncomponent.arg1 = 3;\nconsole.log(component.resultString);\n//Result: 3\n\ncomponent.arg2 = 2;\nconsole.log(component.resultString);\n//Result: 5\n```\n\nAll the class properties form a dependency graph:\n* **sum** depends on **arg1** and **arg2**\n* **resultString** depends on **sum**\n\nOnce any argument has been changed, all dependencies are recalculated (immediately or through *setTimeout*)\n\n\u003ca name=\"glossary\"/\u003e\n\n## 2. Glossary\n\n* **Component** - class for which state tracking is initialized.\n* **State** - snapshot of component property values.\n* **State Difference** - subset of state.\n* **Modifier** - pure function that receives a state and returns a state difference which will patch the component property values. Modifiers are called when the component property values have changed and the current state is not equal to the previous one.\n* **Action** - entity that causes modifier firing without changing the component property values. The entity instance is passed into the triggered modifiers.\n\n\u003ca name=\"api\"/\u003e\n\n## 3. API\n* ### Mapped types\n  * **ComponentState\u0026lt;T\u0026gt;** - removes all methods, private properties. Converts observables into plain properties. All members are compulsory and readonly.\n  * **ComponentStateDiff\u0026lt;T\u0026gt;** - removes all methods, private properties, observables. Converts subjects and event emitters into plain properties. All members are optional and readonly.\n    ```ts\n    class Component {\n        member1: number;\n        member2: Observable\u003cstring\u003e;\n        member3: Subject\u003cnumber\u003e;\n        private member4: number;\n        method1(){}\n    }\n\n    type State = ComponentState\u003cComponent\u003e;\n    //Equals to\n    type State = {\n      readonly member1: number;\n      readonly member2: string | undefined;\n      readonly member3: number | undefined;\n    }\n\n    type StateDiff = ComponentStateDiff\u003cComponent\u003e;\n    //Equals to\n    type StateDiff = {\n      member1?: number;\n      member3?: number;\n    }\n    ```\n  * **StateDiff\u0026lt;T\u0026gt;** - **ComponentStateDiff\u0026lt;T\u0026gt;** or array of actions where the first element can be **ComponentStateDiff\u0026lt;T\u0026gt;**\n\n\u003ca name=\"initialization\" /\u003e\n\n* ### Initialization\n  In order to make modifiers and actions working within a class this class should be initialized  to keep track of its state. It can be done by calling **initializeImmediateStateTracking** or **initializeStateTracking** functions e.g.\n  \n  ```ts\n  class Component {\n    constructor() {\n        initializeStateTracking(this,{/*options*/});\n    }\n  }\n  ```\n  where **options** are:\n  ```ts\n  export type InitStateTrackingOptions\u003cTComponent\u003e = {\n    immediateEvaluation?: boolean | null,\n    includeAllPredefinedFields?: boolean | null,\n    initialState?: ComponentStateDiff\u003cTComponent\u003e | null,\n    sharedStateTracker?: Object | Object[] | null,\n    onStateApplied?: ((state: ComponentState\u003cTComponent\u003e, previousState: ComponentState\u003cTComponent\u003e) =\u003e void) | null,\n    errorHandler?: ((e: Error) =\u003e boolean) | null\n  }\n  ```\n  * **immediateEvaluation** - indicates if modifiers will be called immediately or trough *setTimeout* (avoids redundant calls). Default value is **false** for **initializeStateTracking** and **true** for **initializeImmediateStateTracking**.\n  * **includeAllPredefinedFields** - by default only properties bound to modifiers are included in component property value snapshots (states). This options allows including all properties that had some explicit values (even **null** or **undefined**) at the moment of the initialization. This might be useful to provide some context for modifiers. Default value is **false** for **initializeStateTracking** and **true** for **initializeImmediateStateTracking**\n  * **initialState** - initial snapshot of component property values that will be applied at the initialization.\n  * **sharedStateTracker** - reference to other components with initialized state tracking. changes and actions at that components can be reflected in the initializing component. component can also cause state modification and action firing in the shared components.\n\n  * **onStateApplied** - callback function which is called right after a new state has been applied.\n\n  * **errorHandler** - callback function which is called when some error has been thrown in modifiers or action handlers\n\n  **initializeStateTracking** and **initializeImmediateStateTracking** functions returns a reference to so-called **stateHandler**:\n\n  ```ts\n  class Component {\n\n    stateHandler: IStateHandler\u003cComponent\u003e\n\n    constructor() {\n       this.stateHandler = initializeStateTracking(this,{/*options*/});\n    }\n  }\n  ```\n\n  this is an object that provides API to manipulate the state tracking:\n  ```ts\n  export interface IStateHandler\u003cTComponent\u003e {\n    getState(): ComponentState\u003cTComponent\u003e;\n    modifyStateDiff(diff: StateDiff\u003cTComponent\u003e);\n    subscribeSharedStateChange(): ISharedStateChangeSubscription | null;\n    whenAll(): Promise\u003cany\u003e;\n    execAction\u003cTAction extends StateActionBase\u003e(action: TAction|TAction[]): boolean;\n    release();\n  }\n  ```\n  * **getState()** returns a current snapshot of component property values (included into the state tracking)\n  * **modifyStateDiff(diff: StateDiff\u003cTComponent\u003e);** - patches the current state and fires corresponding modifiers.\n  * **subscribeSharedStateChange()** - if the component is connected with some shared components then calling this function will subscribe the current component on changes and actions in the shared ones.\n  * **whenAll()** - returns a promise which is complete when all asynchronous modifiers are completed.\n  * **execAction(...)** - fires execution of the passed actions\n  * **release()** - unsubscribes from all shared components and Observables\n  \n\u003ca name=\"decorators\" /\u003e\n\n* ### Decorators\n\n\u003ca name=\"with\"/\u003e\n\n  * **@With** - decorator that marks a static(!) class method to be called when any of the specified properties have been just changed. The method should return some new values that will be patched to the component properties and a new state will be formed. It also can return new actions to be executed.\n\n    ```ts\n    //Short syntax\n    @With('arg1', 'arg2')\n    static calc(state: ComponentState\u003cComponent\u003e): StateDiff\u003cComponent\u003e {\n      return {\n        sum: state.arg1 + state.arg2\n      };\n    }\n\n    //Full syntax\n    @With\u003cComponent\u003e('arg1', 'arg2')\n        .Debounce(10/*ms*/)//This modifier is called when the specified properties are stable for 10ms\n        .CallOnInit()//Forcibly call right after the initialization\n        .If(s =\u003e s.arg1 \u003e 0)//Call only is the condition is true\n        .IfNotEqualNull()//Call only all the specified properties are not equal null\n    static calcSum(\n        state: ComponentState\u003cComponent\u003e,//Current state\n        previousState: ComponentState\u003cComponent\u003e,//Previous state\n        diff: ComponentStateDiff\u003cComponent\u003e//Diff between current and previous\n        ): StateDiff\u003cComponent\u003e {\n            \n        return [{\n            sum: state.arg1 + state.arg2\n        }, new Action1(), new Action2()];\n    }\n    ```\n\n\u003ca name=\"with_async\"/\u003e\n\n  * **@WithAsync** - decorator that marks a static(!) class method to be called when any of the specified properties have been just changed. The method should return a promise with some new values that will be patched (when the promise is complete) to the component properties and a new state will be formed.\n    ```ts\n    //Short syntax\n    @WithAsync\u003cComponent\u003e('arg1', 'arg2')\n    static async calcAsync(getState: AsyncContext\u003cComponent\u003e): Promise\u003cStateDiff\u003cComponent\u003e\u003e {\n      const initialState = getState();\n\n      await initialState.service.doSomethingAsync();\n\n      const state = getState();\n\n      return {\n        sum: state.arg1 + state.arg2\n      };\n    }\n\n    //Full syntax\n      @WithAsync\u003cComponent\u003e('arg1', 'arg2')\n        .Debounce(10/*ms*/)//Modifier is called when the specified properties are stable for 10ms\n        .Locks('res1', 'res2')//Modifier will not be called while one the resources are locked by other modifiers\n        .If(s=\u003es.arg1 \u003e 2)//Modifier is called is the condition is true\n        .PreSet(s =\u003e ({loading: true}))//Updates state right before calling the modifier\n        .OnConcurrentLaunchReplace()//Behavior on collision\n        //or .OnConcurrentLaunchPutAfter()\n        //or .OnConcurrentLaunchCancel()\n        //or .OnConcurrentLaunchConcurrent()\n        //or .OnConcurrentLaunchThrowError()\n        .OnErrorCall(s =\u003e ({isError: true}))//Behavior on error\n        //or .OnErrorForget()\n        //or .OnErrorThrow()\n        .Finally(s =\u003e ({loading: true}))//Updates state right after calling the modifier\n\n    static async calcAsync(\n        getState: AsyncContext\u003cComponent\u003e,//Functions that returns the current state\n        previousState: ComponentState\u003cComponent\u003e,//Previous state\n        diff: ComponentStateDiff\u003cComponent\u003e//Diff between current and previous \n        ): Promise\u003cStateDiff\u003cComponent\u003e\u003e {\n        const initialState = getState();\n\n        await initialState.service.doSomethingAsync();\n\n        //Modifier could be canceled due to a collision\n        if(getState.isCancelled()) {\n            return null;\n        }\n\n        const state = getState();\n\n        return [{\n            sum: state.arg1 + state.arg2\n        }, new Action1(), new Action2()];\n    } \n    ```\n    \n    * Behaviors on collision:\n      * **OnConcurrentLaunchReplace** - latest fired modifier will be used\n      * **OnConcurrentLaunchPutAfter()** - latest planned modifier will be triggered when the running one is completed\n      * **OnConcurrentLaunchCancel()** - latest planned modifier will be discarded if the current one is not yet completed\n      * **OnConcurrentLaunchConcurrent()** - all fired modifiers will work simultaneously\n      * **OnConcurrentLaunchThrowError** - if modifier is triggered if the current one is not yet completed then an error will be thrown\n\n\n\u003ca name=\"with_action\"/\u003e\n\n  * **@WithAction** - decorator that marks a static(!) class method to be called when a specified action is asked to be executed. The method should return some new values that will be patched to the component properties and a new state will be formed. It also can return new actions to be executed.\n    ```ts\n    class ActionIncreaseArg1By extends StateActionBase {\n        constructor(readonly value: number) {\n            super();\n        }\n    }\n    ```\n    ```ts\n    @WithAction(ActionIncreaseArg1By)\n    static onAction(\n        action: ActionIncreaseArg1By,\n        state: ComponentState\u003cComponent\u003e): StateDiff\u003cComponent\u003e {\n\n        return {\n            arg1: state.arg1 + action.value\n        };\n    }\n    ```\n\n\u003ca name=\"with_action_async\"/\u003e\n\n  * **@WithActionAsync** - similar to **@WithAsync**\n      ```ts\n      @WithActionAsync(ActionIncreaseArg1By)\n      static async onAction(\n          action: ActionIncreaseArg1By,\n          getState: AsyncContext\u003cComponent\u003e): Promise\u003cStateDiff\u003cComponent\u003e\u003e {\n\n          await getState().service.doSomething();\n\n          const state = getState();\n\n          return {\n              arg1: state.arg1 + action.value\n          };\n      }\n      ```\n\n\u003ca name=\"with_shared_as_source\"/\u003e\n\n* **@WithSharedAsSource** - decorator that marks a static(!) class method to be called when a specified properties have just changed in the specified shared component. The method should return some new values that will be patched to the component properties and a new state will be formed. It also can return new actions to be executed.\n    ```ts\n    @WithSharedAsSource(SharedComponent, 'value').CallOnInit()\n    static onValueChange(arg: WithSharedAsSourceArg\u003cComponent, SharedComponent\u003e)        :StateDiff\u003cComponent\u003e {\n        return {\n            message: `Shared value is ${arg.currentSharedState.value}`\n        };\n\n        //or\n        //return [{\n        //    message: `Shared value is ${arg.currentSharedState.value}`\n        //}, new Action1(),new Action2(),...];\n    }\n    ```\n\n\u003ca name=\"with_shared_as_target\"/\u003e\n\n  * **@WithSharedAsTarget** - decorator that marks a static(!) class method to be called when a specified properties have just changed in the specified shared component. The method should return some new values that will be patched to the component properties and a new state will be formed. It also can return new actions to be executed.\n    ```ts\n    @WithSharedAsTarget(SharedComponent, 'componentValue')\n    static onCmpValueChange(arg: WithSharedAsTargetArg\u003cComponent, SharedComponent\u003e): StateDiff\u003cSharedComponent\u003e {\n        return {\n            value: arg.currentState.componentValue * 10\n        };\n        //or\n        //return [{\n        //    value: arg.currentState.componentValue * 10\n        //}, new Action1(),new Action2(),...];\n    }\n    ```\n\n\u003ca name=\"async_init\"/\u003e\n\n  * **@AsyncInit** - decorator that marks a static(!) class method to be called right after the initialization. The method should return a promise with some new values that will be patched (when the promise is complete) to the component properties and a new state will be formed.\n    ```ts\n    @AsyncInit()\n      //.Locks('res1', 'res2') - similar to @WithAsync\n    static async init(getState: AsyncContext\u003cComponent\u003e): Promise\u003cStateDiff\u003cComponent\u003e\u003e {\n\n        const state = getState();\n\n        const greetingFormat = await state.services.getGreetingFormat();\n\n        return { greetingFormat };\n\n        //or\n        //return [{ greetingFormat }, new Action1(), new Action2()];\n    }\n    ```\n\n\u003ca name=\"emitter\"/\u003e\n\n  * **@Emitter()** - decorator for a class property. If this decorator is specified for some class property that means that all side effects will happen (@With, @WithAsync, @Out) even if a new value equals to the previous one during analyzing a new state difference.\n\n\u003ca name=\"bind_to_shared\" /\u003e\n\n  * **@BindToShared([SharedClass], [sharedPropName],[index])** - It marks a component field to be a proxy to a shared tracker filed.\n    * SharedClass - specifies a shared component type\n    * propName - specifies a shared property name in case if the name differs.\n    * index - if a component is bound to several shared trackers then this option will help to solve a possible naming conflict.\n    \n    ```ts\n    @BindToShared(SharedComponent, 'value')\n    sharedValue: number;\n    ```\n\u003ca name=\"include_in_state\"\u003e\n\n* **@IncludeInState()** - It marks a component field to be always included into snapshots.\n\n\u003ca name=\"examples\"/\u003e\n\n## 4. Examples\n### Shared Components\n```ts\nclass SharedComponent {\n    value: number = 0;\n\n    constructor() {\n        initializeImmediateStateTracking(this);\n    }\n}\n\nclass Component {\n                \n    message = '';\n\n    componentValue = 0;\n\n    @BindToShared(SharedComponent, 'value')\n    sharedValue: number;\n\n    constructor(shared: SharedComponent) {\n        initializeImmediateStateTracking(this, {\n            sharedStateTracker: shared\n        }).subscribeSharedStateChange();\n    }\n\n    onDestroy() {\n        releaseStateTracking(this);\n    }\n\n    @WithSharedAsSource(SharedComponent, 'value').CallOnInit()\n    static onValueChange(arg: WithSharedAsSourceArg\u003cComponent, SharedComponent\u003e): StateDiff\u003cComponent\u003e {\n        return {\n            message: `Shared value is ${arg.currentSharedState.value}`\n        };\n    }\n\n    @WithSharedAsTarget(SharedComponent, 'componentValue')\n    static onCmpValueChange(arg: WithSharedAsTargetArg\u003cComponent, SharedComponent\u003e): StateDiff\u003cSharedComponent\u003e {\n        return {\n            value: arg.currentState.componentValue * 10\n        };\n    }\n}\n\nconst sharedComponent = new SharedComponent();\nconst component = new Component(sharedComponents);\n\nconsole.log(component.message);\n//Shared value is 0\n\nsharedComponent.value = 7;\nconsole.log(component.message);\n//Shared value is 7\n\ncomponent.componentValue = 10;\nconsole.log(component.message);\n//Shared value is 100\n\ncomponent.sharedValue = 10;\nconsole.log(component.message);\n//Shared value is 10\n\ncomponent.onDestroy();\n```\n\n\u003ca name=\"async\"/\u003e\n\n### Async\n```ts\nclass Component {\n\n    name = '';\n\n    greetingFormat = '';\n\n    greeting = '';\n\n    constructor() {\n        initializeImmediateStateTracking(this);\n    }\n\n    @AsyncInit().Locks('init')\n    static async init(getState: AsyncContext\u003cComponent\u003e): Promise\u003cStateDiff\u003cComponent\u003e\u003e {\n        await delayMs(100);\n\n        return {\n            greetingFormat: 'Hi, %USER%!'\n        };\n    }\n\n    @WithAsync('name').Locks('init')\n    static async createGreeting(getState: AsyncContext\u003cComponent\u003e): Promise\u003cStateDiff\u003cComponent\u003e\u003e {\n\n        await delayMs(10);\n\n        const state = getState();\n\n        return {\n            greeting: state.greetingFormat.replace('%USER%', state.name)\n        };\n    }\n}\n\nconst component = new Component();\n\ncomponent.name = 'Joe';\n\nawait getStateHandler(component).whenAll();\n\nconsole.log(component.greeting);\n//Hi, Joe!\n```\n\u003ca name=\"actions\"/\u003e\n\n### Actions\n```ts\nclass ActionA extends StateActionBase {\n    constructor(readonly message: string) {\n        super();\n    }\n}\n\nclass ActionB extends StateActionBase {\n    constructor(readonly message: string) {\n        super();\n    }\n}\n\nclass ActionS extends StateActionBase {\n    constructor(readonly message: string) {\n        super();\n    }\n}\n\ntype Logger = {\n    log: (s: string) =\u003e void;\n}\n\nclass SharedComponent {\n    stateHandler: IStateHandler\u003cSharedComponent\u003e;\n\n    constructor(readonly logger: Logger) {\n        this.stateHandler = \n            initializeImmediateStateTracking\u003cSharedComponent\u003e(this);\n    }\n\n    @WithAction(ActionS)\n    static onActionS(action: ActionS, state: ComponentState\u003cSharedComponent\u003e)\n        : StateDiff\u003cSharedComponent\u003e {\n\n            state.logger.log(`Action S with arg \"${action.message}\"`);\n\n        return [new ActionB(action.message + ' from Shared')];\n    }\n}\n\nclass Component {\n    arg = ''\n\n    stateHandler: IStateHandler\u003cComponent\u003e;\n\n    constructor(readonly logger: Logger,sharedComponent: SharedComponent) {\n\n        this.stateHandler\n            = initializeImmediateStateTracking\u003cComponent\u003e(this, {\n                sharedStateTracker: sharedComponent\n            });\n\n        this.stateHandler.subscribeSharedStateChange();\n    }\n\n    onDestroy() {\n        this.stateHandler.release();\n    }\n\n    @With('arg')\n    static onNameChange(state: ComponentState\u003cComponent\u003e): StateDiff\u003cComponent\u003e {\n        return [new ActionA(state.arg)];\n    }\n\n    @WithAction(ActionA)\n    static onActionA(action: ActionA, state: ComponentState\u003cComponent\u003e): StateDiff\u003cComponent\u003e {\n\n        state.logger.log(`Action A with arg \"${action.message}\"`);\n\n        return [new ActionB(action.message), new ActionS(action.message)];\n    }\n\n    @WithAction(ActionB)\n    static onActionB(action: ActionB, state: ComponentState\u003cComponent\u003e): StateDiff\u003cComponent\u003e {\n        state.logger.log(`Action B with arg \"${action.message}\"`);\n        return null;\n    }\n}\n\nconst logger: Logger = {\n    log: (m) =\u003e console.log(m)\n};\n\nconst sharedComponent = new SharedComponent(logger);\n\nconst component = new Component(logger, sharedComponent);\n\ncomponent.arg = \"arg1\";\n//Action B with arg \"arg1\"\n//Action S with arg \"arg1\"\n//Action B with arg \"arg1 from Shared\"\n\ncomponent.stateHandler.execAction(new ActionS('arg2'));\n//Action S with arg \"arg2\"\n//Action B with arg \"arg2 from Shared\"\n\nsharedComponent.stateHandler.execAction(new ActionA('arg3'));\n//Action A with arg \"arg3\"\n//Action B with arg \"arg3\"\n```\n\u003ca name=\"observables\"/\u003e\n\n### Observables\n\n```ts\nclass Component {\n\n    sum: Subject\u003cnumber\u003e;\n\n    constructor(readonly arg1: Observable\u003cnumber\u003e,readonly  arg2: Observable\u003cnumber\u003e) {\n\n        this.sum = new Subject\u003cnumber\u003e();\n\n        initializeImmediateStateTracking\u003cComponent\u003e(this);\n    }\n\n    destroy() {\n        releaseStateTracking(this);\n    }\n\n    @With\u003cComponent\u003e('arg1', 'arg2')\n    static calcSum(s: ComponentState\u003cComponent\u003e): StateDiff\u003cComponent\u003e {\n        return {\n            sum: (s.arg1??0) +(s.arg2??0)\n        };\n    }\n}\n\nconst arg1 = new BehaviorSubject\u003cnumber\u003e(0);\nconst arg2 = new BehaviorSubject\u003cnumber\u003e(0);\n\nconst component = new Component(arg1, arg2);\n\nlet sum: number = 0;\ncomponent.sum.subscribe( s=\u003e sum = s);\n\narg1.next(2);\n\nconsole.log(sum.toString());\n//2\n\narg2.next(3);\n\nconsole.log(sum.toString())\n//5;\n\narg2.next(10);\n\nconsole.log(sum.toString())\n//12;\n```\n\n\u003ca name=\"using-in-react\"/\u003e\n\n### Using in React\n\n[React Demo (stackblitz.com)](https://stackblitz.com/edit/react-ts-yz6kuo?file=AppStateTrack.ts)\n\n```ts\nimport React, { ChangeEventHandler } from 'react';\nimport './App.css';\nimport { ComponentState, With, IStateHandler, StateDiff, initializeImmediateStateTracking } from 'ng-set-state';\n\ntype State = ComponentState\u003cComponentStateTrack\u003e;\ntype NewState = StateDiff\u003cComponentStateTrack\u003e;\n\nclass ComponentStateTrack {\n    arg1Text = '';\n    arg1Status = 'Empty';\n    arg2Text = '';\n    arg2Status = 'Empty';\n    sumText = 'No proper args'\n\n    arg1: number|null = null;\n    arg2: number|null = null;\n\n    stateHandler: IStateHandler\u003cComponentStateTrack\u003e;\n\n    constructor(stateSetter: (s: State) =\u003e void) {\n      this.stateHandler = initializeImmediateStateTracking\u003cComponentStateTrack\u003e(this,\n        {\n            onStateApplied: (s) =\u003e stateSetter(s)\n        });\n    }\n\n    @With('arg1Text')\n    static parseArg1(state: State): NewState {\n\n        if (!state.arg1Text) {\n            return {\n                arg1: null,\n                arg1Status: \"Empty\"\n            }\n        }\n\n        const arg1 = parseInt(state.arg1Text);\n        if(isNaN(arg1)) {\n            return {\n                arg1: null,\n                arg1Status: \"Invalid\"\n            };\n        } else {\n            return {\n                arg1,\n                arg1Status: \"Ok\"\n            };\n        }\n    }\n\n    @With('arg2Text')\n    static parseArg2(state: State): NewState {\n        if (!state.arg2Text) {\n            return {\n                arg1: null,\n                arg1Status: \"Empty\"\n            }\n        }\n\n        const arg2 = parseInt(state.arg2Text);\n        if(isNaN(arg2)) {\n            return {\n                arg2: null,\n                arg2Status: \"Invalid\"\n            };\n        } else {\n            return {\n                arg2,\n                arg2Status: \"Ok\"\n            };\n        }\n    }\n\n    @With('arg1', 'arg2')\n    static setCalcStatus(state: State): NewState {\n        if (state.arg1 == null || state.arg2 == null) {\n            return {\n                sumText: 'No proper args'\n            };\n        } else {\n            return {\n                sumText: 'Calculating...'\n            }\n        }\n    }\n\n    @With('arg1', 'arg2').Debounce(2000/*ms*/)\n    static setCalcResult(state: State): NewState {\n        if (state.arg1 != null \u0026\u0026 state.arg2 != null) {\n            return {\n                sumText: (state.arg1 + state.arg2).toString()\n            };\n        }\n        return null;\n    }\n}\n\nexport class App extends React.Component\u003cany, State\u003e {\n\n  readonly stateTrack: ComponentStateTrack;\n\n  constructor(props: any) {        \n      super(props);\n      this.stateTrack = new ComponentStateTrack(s =\u003e this.setState(s));\n      this.state = this.stateTrack.stateHandler.getState();\n  }\n\n  arg1Change: ChangeEventHandler\u003cHTMLInputElement\u003e = (ev) =\u003e {\n      this.stateTrack.arg1Text = ev.target.value;\n  };\n\n  arg2Change: ChangeEventHandler\u003cHTMLInputElement\u003e = (ev) =\u003e {\n      this.stateTrack.arg2Text = ev.target.value;\n  };\n\n  render = () =\u003e {\n      return (\n          \u003cdiv\u003e\n            \u003cdiv\u003e\n              Arg 1: \u003cinput onChange={this.arg1Change} /\u003e {this.state.arg1Status}\n            \u003c/div\u003e\n            \u003cdiv\u003e\n              Arg 2: \u003cinput onChange={this.arg2Change}/\u003e {this.state.arg2Status}\n            \u003c/div\u003e\n            \u003cdiv\u003e\n              Result: { this.state.sumText }\n             \u003c/div\u003e\n          \u003c/div\u003e\n        );\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0x1000000%2Fngsetstate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0x1000000%2Fngsetstate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0x1000000%2Fngsetstate/lists"}