{"id":26116595,"url":"https://github.com/sourcegeneration/changetracking","last_synced_at":"2025-04-13T06:50:46.463Z","repository":{"id":212806105,"uuid":"732358623","full_name":"SourceGeneration/ChangeTracking","owner":"SourceGeneration","description":"Source generator based state management library without the reflection","archived":false,"fork":false,"pushed_at":"2025-03-10T02:23:34.000Z","size":194,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T23:11:16.158Z","etag":null,"topics":["blazor","change-detection","change-tracker","changetracking","collectionchanged","csharp","dotnet","partial-property","propertychanged","sourcegeneration","sourcegenerator","state","state-management"],"latest_commit_sha":null,"homepage":"https://github.com/SourceGeneration/ChangeTracking","language":"C#","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/SourceGeneration.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}},"created_at":"2023-12-16T12:15:33.000Z","updated_at":"2025-03-10T02:23:38.000Z","dependencies_parsed_at":"2023-12-16T13:08:50.413Z","dependency_job_id":"35b3736d-1f10-42cd-8d26-c2a296f8428f","html_url":"https://github.com/SourceGeneration/ChangeTracking","commit_stats":null,"previous_names":["sourcegeneration/states","sourcegeneration/changetracking"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SourceGeneration%2FChangeTracking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SourceGeneration%2FChangeTracking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SourceGeneration%2FChangeTracking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SourceGeneration%2FChangeTracking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SourceGeneration","download_url":"https://codeload.github.com/SourceGeneration/ChangeTracking/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248675458,"owners_count":21143766,"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":["blazor","change-detection","change-tracker","changetracking","collectionchanged","csharp","dotnet","partial-property","propertychanged","sourcegeneration","sourcegenerator","state","state-management"],"created_at":"2025-03-10T09:47:02.995Z","updated_at":"2025-04-13T06:50:46.444Z","avatar_url":"https://github.com/SourceGeneration.png","language":"C#","readme":"# ChangeTracking\n\n[![NuGet](https://img.shields.io/nuget/vpre/SourceGeneration.ChangeTracking.svg)](https://www.nuget.org/packages/SourceGeneration.ChangeTracking)\n\nSourceGeneration.ChangeTracking is a state management framework based on Source Generator, it supports `AOT` compilation.\n\n## Installing\n\nThis library uses C# preview features `partial property`, Before using this library, please ensure the following prerequisites are met:\n- Visual Studio is version 17.11 preview 3 or higher.\n- To enable C# language preview in your project, add the following to your .csproj file\n```c#\n\u003cPropertyGroup\u003e  \n  \u003cLangVersion\u003epreview\u003c/LangVersion\u003e  \n\u003c/PropertyGroup\u003e  \n```\n\n```powershell\nInstall-Package SourceGeneration.ChangeTracking -Version 1.0.0-beta4.250107.1\n```\n\n```powershell\ndotnet add package SourceGeneration.ChangeTracking --version 1.0.0-beta4.250107.1\n```\n\n## Start\n\nStates source generator will generate partial class for your state type, you just need to add `ChangeTrackingAttriute`, The state type must be `partial`, The property must be `partial` and have a setter\n\n```c#\n[ChangeTracking]\npublic partial class Goods\n{\n    public Goods()\n    {\n        Price = 1.0;\n    }\n\n    public partial int Number { get; set; }\n    public partial double Price { get; set; }\n    public partial int Count { get; set; }\n}\n```\n\nThe partial class implement `INotifyPropertyChanging`, `INotifyPropertyChanged` and `IChangeTracking`\n\n```c#\npublic partial class Goods : INotifyPropertyChanging, INotifyPropertyChanged, System.ComponentModel.IChangeTracking\n{\n    //Properties partial implementation\n}\n```\n\nStates determines whether an object has been modified through two methods:\n- Checking if the object reference has changed.\n- Checking IChangeTracking.IsChanged property.\n\n## State\nBased on ChangeTracking, we can build a state that subscribes to changes.\n```c#\n[ChangeTracking]\npublic partial class Goods : State\u003cGoods\u003e\n{\n    public Goods()\n    {\n        Price = 1.0;\n    }\n\n    public partial int Number { get; set; }\n    public partial double Price { get; set; }\n    public partial int Count { get; set; }\n}\n```\nThe State class can create a IChangeTracker.\n```c#\nGoods state = new Goods();\nint currentCount = 0;\n\n//Create a IChangeTracker to tracking state changes\nvar tracker = state.CreateTracker();\n\n//Watch price and count property\ntracker.Watch(x =\u003e x.Price, x =\u003e Console.WriteLine($\"Price has changed: {x}\"));\ntracker.Watch(x =\u003e x.Count, x =\u003e Console.WriteLine($\"Count has changed: {x}\"));\n\nstate.Count++;\nstate.AcceptChanges(); // output: Count has changed: 1\n\nstate.Price = 3.14;\nstate.AcceptChanges(); // output: Price has changed: 3.14\n\nstate.Number = 1;\nstate.AcceptChanges(); // no output, because the Number property was not watch\n\nstate.Count = 1;\nstate.AcceptChanges(); // no output, because the Count property has not changed\n\n```\n\n## Predicate\n\n```c#\n\ntracker.Watch(\n    selector: x =\u003e x.Count,\n    predicate: x =\u003e x \u003e= 10,\n    subscriber: x =\u003e Console.WriteLine($\"Count changed: {x}\"));\n\n// no console ouput, the value is less than 10\nstate.Count = 9;\nstate.AcceptChanges();\n\n// ouput Count changed: 10\nstate.Count = 10;\nstate.AcceptChanges();\n\n```\n\n## Change Scope\nStates support change scope, You can specify the scope of the subscribed changes.\n\n- **ChangeTrackingScope.Root** `default value`  \n  The subscription only be triggered when there are changes in the properties of the object itself.\n- **ChangeTrackingScope.Cascading**  \n  The subscription will be triggered when there are changes in the properties of the object itself or in the properties of its property objects.\n- **ChangeTrackingScope.Always**  \n  The subscription will be triggered whenever the `Update` method is called, regardless of whether the value has changed or not.\n\n```c#\n[ChangeTracking]\npublic partial class Goods : State\u003cGoods\u003e\n{\n    public Goods()\n    {\n        Tags = [];\n    }\n\n    public partial ChangeTrackingList\u003cSubState\u003e Tags { get; set; }\n}\n\n[ChangeTracking]\npublic partial class SubState\n{\n    public partial string? Tag { get; set; }\n}\n```\n\n```c#\n// Watch Tags with scope `ChangeTrackingScope.Root`, it's default value\n// The state will push last value when you subscribed\n// ouput: Tags count has changed 0\nvar disposable = tracker.Watch(\n    selector: x =\u003e x.Tags, \n    subscriber: x =\u003e Console.WriteLine($\"Tags count has changed: {x.Count}\"), \n    scope: ChangeTrackingScope.Root);\n\n// output: Tags count has changed: 1\nstate.Tags.Add(new SubState { Tag = \"first tag\" });\nstate.AcceptChanges();\n\n// no output, because Tags property is not changed\nstate.Tags[0].Tag = \"first tag has modified\";\nstate.AcceptChanges();\n\ndisposable.Dispose();\n\n// Watch Tags with scope `ChangeTrackingScope.Cascading`\n// The state will push last value when you subscribed\n// ouput: Tags value has changed: first tag has modified\ntracker.Watch(\n    selector: x =\u003e x.Tags,\n    subscriber: x =\u003e Console.WriteLine($\"Tags value has changed: {x[0].Tag}\"),\n    scope: ChangeTrackingScope.Cascading);\n\n// ouput: Tags value has changed: first tag has modified * 2\nstate.Tags[0].Tag = \"first tag has modified * 2\";\nstate.AcceptChanges();\n```\n\n## Merge Changes\n\nSome times we need to merge all changes, \nyou can use `OnChange`\n\n```c#\n\nMyState state = new MyState();\nvar tracker = state.CreateTracker();\n\ntracker.Watch(x =\u003e x.Count);\ntracker.Watch(x =\u003e x.Price);\n\ntracker.OnChange(state =\u003e\n{\n    Console.WriteLine($\"Count or Price has changed. Count={count}, Price={state.Price}\");\n});\n\n//ouput: Count or Price has changed\nstate.Price = 3.14;\nstate.Count = 10;\nstate.AcceptChanges();\n\n//no output, because Count has not changed\nstate.Count = 10;\nstate.AcceptChanges();\n\n//no output, because property Number has not subscribed \nstate.Number = 3;\nstate.AcceptChanges();\n\n//ouput: Count or Price has changed\nstate.Count = 11;\nstate.AcceptChanges();\n```\n\n## DependencyInjection\n\nThe State class only has a parameterless constructor, making it easy to use dependency injection.\n\n```c#\n\n[ChangeTracking]\npublic partial class MyState(ILogger\u003cMyState\u003e logger) : State\u003cMyState\u003e\n{\n    public partial int Count { get; set; }\n\n    public void Increment()\n    {\n        Count++;\n        State.AcceptChanges();\n        logger.LogInformation(\"Count Increment\");\n    }\n}\n\nvar services = new ServiceCollection();\nservices.AddLogging();\nservices.AddScoped\u003cGoods\u003e();\nservices.AddSingleton\u003cMyState\u003e();\n```\n\n## Dispose \u0026 Unsubscribe\n\nIn most usage scenarios, when your page or component subscribes to the state, it must explicitly unsubscribe when the component is destroyed, otherwise it will result in a significant resource consumption.\n\n```c#\nGoods state = new();\nvar tracker = state.CreateTracker();\nvar disposable1 = state.Watch(x =\u003e x.Count);\nvar disposable2 = state.Watch(x =\u003e x.Tags.Count);\nvar disposable3 = state.OnChange(x =\u003e { });\n\ndisposable1.Dispose(); // unsubscribe: Count property watch\ndisposable2.Dispose(); // unsubscribe: Tags.Count property watch\ndisposable3.Dispose(); // unsubscribe: merge changed subscribe\ntracker.Dispose(); // dispose tracker\n```\n\n## Use in Blazor \n\nYou can use `States` in `Blazor`, it supports `AOT` compilation\n\n```c#\n//WebAssembly or Hybird\nservices.AddSingleton\u003cGoods\u003e();\n\n//Server\nservices.AddScoped\u003cGoods\u003e();\n```\n\n**Inject state into component**\n```razor\n@inject Goods State\n@implements IDisposable\n\n\u003ch1\u003eCount: @State.Count\u003c/h1\u003e\n\u003cbutton @onclick=\"Click\"\u003eAdd\u003c/button\u003e\n\n@code{\n    private IChangeTracker Tracker;\n\n    protected override void OnInitialized()\n    {\n        Tracker = State.CreateTracker();\n        Tracker.Watch(x =\u003e x.Count);\n        Tracker.OnChange(StateHasChanged);\n    }\n\n    private void Click()\n    {\n        State.Count++;\n        State.AcceptChanges();\n    }\n\n    public void Dispose()\n    {\n        Tracker.Dispose();\n    }\n}\n```\n\nYou can use the SourceGeneration.Blazor library to simplify this process, more information see [**SourceGeneration.Blazor.Statity**](https://github.com/SourceGeneration/Blazor) repo\n\n[![NuGet](https://img.shields.io/nuget/vpre/SourceGeneration.Blazor.Statity.svg)](https://www.nuget.org/packages/SourceGeneration.Blazor.Statity)\n\n```c#\n@inherits StateComponentBase\n@inject Goods State\n\n\u003ch1\u003eCount: @State.Count\u003c/h1\u003e\n\u003cbutton @onclick=\"Click\"\u003eAdd\u003c/button\u003e\n\n@code{\n    private int Count;\n\n    protected override void OnStateBinding()\n    {\n        Watch(State, x =\u003e x.Count);\n    }\n\n    private void Click()\n    {\n        State.Count++;\n        State.AcceptChanges();\n    }\n}\n\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsourcegeneration%2Fchangetracking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsourcegeneration%2Fchangetracking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsourcegeneration%2Fchangetracking/lists"}