From e7f8797e611d0b7a751b18feae94cf6a01fd59fc Mon Sep 17 00:00:00 2001
From: Eric Simonton <8042088+ersimont@users.noreply.github.com>
Date: Mon, 1 Jan 2024 14:58:38 -1000
Subject: [PATCH] feat(signal-state): Introducing a new library to the S-Libs
family: Signal State! A state management library similar to App State, built
on Angular Signals instead of RxJS.
---
.../signal_store___build.xml | 13 ++
.../signal_store___test_server.xml | 13 ++
README.md | 1 +
angular.json | 39 ++++
package.json | 4 +
projects/app-state/README.md | 65 +-----
.../src/app/api-tests/signal-store.spec.ts | 15 ++
.../integration/src/app/app.component.html | 4 +-
projects/integration/src/app/app.config.ts | 3 +-
projects/integration/src/app/app.routes.ts | 5 +
.../deep-performance.component.html | 6 +
.../deep-performance.component.ts | 39 ++++
.../signal-store-performance.component.html | 3 +
.../signal-store-performance.component.scss | 0
.../signal-store-performance.component.ts | 13 ++
.../wide-performance.component.html | 6 +
.../wide-performance.component.ts | 39 ++++
projects/ng-app-state/README.md | 2 +
projects/signal-store/.eslintrc.json | 14 ++
projects/signal-store/README.md | 117 ++++++++++
projects/signal-store/ng-package.json | 8 +
projects/signal-store/package.json | 22 ++
.../signal-store/src/lib/child-store.spec.ts | 38 ++++
projects/signal-store/src/lib/child-store.ts | 35 +++
projects/signal-store/src/lib/index.ts | 4 +
.../signal-store/src/lib/root-store.spec.ts | 36 +++
projects/signal-store/src/lib/root-store.ts | 18 ++
projects/signal-store/src/lib/store.spec.ts | 207 ++++++++++++++++++
projects/signal-store/src/lib/store.ts | 89 ++++++++
projects/signal-store/src/lib/utils/index.ts | 1 +
.../src/lib/utils/persistent-store.spec.ts | 142 ++++++++++++
.../src/lib/utils/persistent-store.ts | 168 ++++++++++++++
.../src/performance/counter-state.ts | 3 +
.../src/performance/deep-performance.ts | 76 +++++++
.../src/performance/performance-utils.ts | 14 ++
.../src/performance/performance.spec.ts | 60 +++++
.../src/performance/wide-performance.ts | 64 ++++++
projects/signal-store/src/public-api.ts | 6 +
.../src/test-helpers/readme.spec.ts | 67 ++++++
.../src/test-helpers/test-state.ts | 13 ++
projects/signal-store/tsconfig.lib.json | 12 +
projects/signal-store/tsconfig.lib.prod.json | 10 +
projects/signal-store/tsconfig.spec.json | 9 +
scripts/shared.ts | 1 +
44 files changed, 1442 insertions(+), 62 deletions(-)
create mode 100644 .idea/runConfigurations/signal_store___build.xml
create mode 100644 .idea/runConfigurations/signal_store___test_server.xml
create mode 100644 projects/integration/src/app/api-tests/signal-store.spec.ts
create mode 100644 projects/integration/src/app/signal-store-performance/deep-performance/deep-performance.component.html
create mode 100644 projects/integration/src/app/signal-store-performance/deep-performance/deep-performance.component.ts
create mode 100644 projects/integration/src/app/signal-store-performance/signal-store-performance.component.html
create mode 100644 projects/integration/src/app/signal-store-performance/signal-store-performance.component.scss
create mode 100644 projects/integration/src/app/signal-store-performance/signal-store-performance.component.ts
create mode 100644 projects/integration/src/app/signal-store-performance/wide-performance/wide-performance.component.html
create mode 100644 projects/integration/src/app/signal-store-performance/wide-performance/wide-performance.component.ts
create mode 100644 projects/signal-store/.eslintrc.json
create mode 100644 projects/signal-store/README.md
create mode 100644 projects/signal-store/ng-package.json
create mode 100644 projects/signal-store/package.json
create mode 100644 projects/signal-store/src/lib/child-store.spec.ts
create mode 100644 projects/signal-store/src/lib/child-store.ts
create mode 100644 projects/signal-store/src/lib/index.ts
create mode 100644 projects/signal-store/src/lib/root-store.spec.ts
create mode 100644 projects/signal-store/src/lib/root-store.ts
create mode 100644 projects/signal-store/src/lib/store.spec.ts
create mode 100644 projects/signal-store/src/lib/store.ts
create mode 100644 projects/signal-store/src/lib/utils/index.ts
create mode 100644 projects/signal-store/src/lib/utils/persistent-store.spec.ts
create mode 100644 projects/signal-store/src/lib/utils/persistent-store.ts
create mode 100644 projects/signal-store/src/performance/counter-state.ts
create mode 100644 projects/signal-store/src/performance/deep-performance.ts
create mode 100644 projects/signal-store/src/performance/performance-utils.ts
create mode 100644 projects/signal-store/src/performance/performance.spec.ts
create mode 100644 projects/signal-store/src/performance/wide-performance.ts
create mode 100644 projects/signal-store/src/public-api.ts
create mode 100644 projects/signal-store/src/test-helpers/readme.spec.ts
create mode 100644 projects/signal-store/src/test-helpers/test-state.ts
create mode 100644 projects/signal-store/tsconfig.lib.json
create mode 100644 projects/signal-store/tsconfig.lib.prod.json
create mode 100644 projects/signal-store/tsconfig.spec.json
diff --git a/.idea/runConfigurations/signal_store___build.xml b/.idea/runConfigurations/signal_store___build.xml
new file mode 100644
index 00000000..1ff7f4a0
--- /dev/null
+++ b/.idea/runConfigurations/signal_store___build.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/signal_store___test_server.xml b/.idea/runConfigurations/signal_store___test_server.xml
new file mode 100644
index 00000000..1fb67e8c
--- /dev/null
+++ b/.idea/runConfigurations/signal_store___test_server.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 104c24c7..dfe9e8d3 100644
--- a/README.md
+++ b/README.md
@@ -11,5 +11,6 @@ This is a collection of libraries from Simonton Software, written in TypeScript
- [`ng-core`](https://github.com/simontonsoftware/s-libs/tree/master/projects/ng-core): Miscellaneous utilities for Angular
- [`ng-app-state`](https://github.com/simontonsoftware/s-libs/tree/master/projects/ng-app-state): Painlessly integrate `app-state` into template-driven Angular forms
- [`ng-mat-core`](https://github.com/simontonsoftware/s-libs/tree/master/projects/ng-mat-core): Miscellaneous utilities for Angular Material
+- [`signal-store`](https://github.com/simontonsoftware/s-libs/tree/master/projects/signal-store): A state management library based on Angular signals
- [`ng-dev`](https://github.com/simontonsoftware/s-libs/tree/master/projects/ng-dev): Miscellaneous utilities to use while developing Angular apps
- [`eslint-config-ng`](https://github.com/simontonsoftware/s-libs/tree/master/projects/eslint-config-ng): Recommended default config for ESLint in an Angular project.
diff --git a/angular.json b/angular.json
index 6a90ce32..b970c2d7 100644
--- a/angular.json
+++ b/angular.json
@@ -475,6 +475,45 @@
}
}
}
+ },
+ "signal-store": {
+ "projectType": "library",
+ "root": "projects/signal-store",
+ "sourceRoot": "projects/signal-store/src",
+ "prefix": "s",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:ng-packagr",
+ "options": {
+ "project": "projects/signal-store/ng-package.json"
+ },
+ "configurations": {
+ "production": {
+ "tsConfig": "projects/signal-store/tsconfig.lib.prod.json"
+ },
+ "development": {
+ "tsConfig": "projects/signal-store/tsconfig.lib.json"
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "tsConfig": "projects/signal-store/tsconfig.spec.json",
+ "polyfills": ["zone.js", "zone.js/testing"]
+ }
+ },
+ "lint": {
+ "builder": "@angular-eslint/builder:lint",
+ "options": {
+ "lintFilePatterns": [
+ "projects/signal-store/**/*.ts",
+ "projects/signal-store/**/*.html"
+ ]
+ }
+ }
+ }
}
},
"schematics": {
diff --git a/package.json b/package.json
index 62b78ef2..f25671a5 100644
--- a/package.json
+++ b/package.json
@@ -109,6 +109,10 @@
"filename": "projects/ng-mat-core/package.json",
"type": "json"
},
+ {
+ "filename": "projects/signal-store/package.json",
+ "type": "json"
+ },
{
"filename": "projects/rxjs-core/package.json",
"type": "json"
diff --git a/projects/app-state/README.md b/projects/app-state/README.md
index c505e9c8..b770b08b 100644
--- a/projects/app-state/README.md
+++ b/projects/app-state/README.md
@@ -1,4 +1,4 @@
-An observable state management library. Think of it like Redux, but without actions or reducers. That makes it a natural fit for anyone who wants the benefits of centralized state management, without adopting a function style of programming.
+An observable state management library. Directly read, write, and observe any part of your state without writing any selectors, actions, or reducers.
## API Documentation
@@ -9,11 +9,11 @@ Once you are familiar with the basics, it may help to see the [api documentation
A basic idea behind this library is to keep all the state of your app in one place, accessible for any component or service to access, modify and subscribe to changes. This has several benefits:
- Components no longer need multiple inputs and outputs to route state and mutations to the proper components. Instead they can obtain the store via dependency injection.
-- During debugging you can look in one place to see the state of your entire app. Moreover, development tools can be used to see this information at a glance along with a full history of changes leading up to the current state ([Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en)).
+- During debugging, you can look in one place to see the state of your entire app. Moreover, development tools can be used to see this information at a glance along with a full history of changes leading up to the current state ([Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en)).
- The objects in the store are immutable (as long as you only modify the state via the store, as you should), which enables more benefits:
- - Immutable objects allow you to use Angular's on-push change detection, which can be a huge performance gain for apps with a large state.
+ - Immutable objects allow efficient comparison using `===` to determine if they changed. Note that if you are using Angular this enables on-push change detection, but you should consider the newer [`signal-store`](https://github.com/simontonsoftware/s-libs/tree/master/projects/signal-store) instead!
- Undo/redo features become very simple. This library includes a helper to make it even easier (more info below).
-- Every piece of state is observable. You can subscribe to the root of the store to get notified of every state change anywhere in the app, for a specific boolean buried deep within your state, or anywhere in between.
+- Every piece of state is observable. You can subscribe to the root of the store to get notified of every state change anywhere in the app, a specific boolean buried deep within your state, or anywhere in between.
2 terms are worth defining immediately. As they are used in this library, they mean:
@@ -52,16 +52,14 @@ export class User {
}
```
-Then create a subclass of `RootStore`. A single instance of that class will serve as the entry point to obtain and modify the state it holds. Most often you will make that class an Angular service that can be injected anywhere in your app. For example:
+Then create a subclass of `RootStore`. A single instance of that class will serve as the entry point to obtain and modify the state it holds.
```ts
-// state/my-store.service.ts
+// state/my-store.ts
-import { Injectable } from "@angular/core";
import { RootStore } from "@s-libs/app-state";
import { MyState } from "./my-state";
-@Injectable({ providedIn: "root" })
export class MyStore extends RootStore {
constructor() {
super(new MyState());
@@ -69,56 +67,6 @@ export class MyStore extends RootStore {
}
```
-## Usage
-
-Consider this translation of the counter example from the `ngrx/store` readme:
-
-```ts
-// app-state.ts
-export class AppState {
- counter = 0;
-}
-
-// app-store.ts
-@Injectable({ providedIn: "root" })
-export class AppStore extends RootStore {
- constructor() {
- super(new AppState());
- }
-}
-
-// my-app-component.ts
-@Component({
- selector: "my-app",
- template: `
-
-
Current Count: {{ counterStore.$ | async }}
-
-
-
- `,
-})
-export class MyAppComponent {
- counterStore: Store;
-
- constructor(store: AppStore) {
- this.counterStore = store("counter");
- }
-
- increment() {
- this.counterStore.set(this.counterStore.state() + 1);
- }
-
- decrement() {
- this.counterStore.set(this.counterStore.state() - 1);
- }
-
- reset() {
- this.counterStore.set(0);
- }
-}
-```
-
## Style Guide
- Define your state using classes instead of interfaces, and when possible make `new StateObject()` come with the default values for all its properties.
@@ -139,7 +87,6 @@ export class MyAppComponent {
This package includes an abstract class, `UndoManager`, to assist you in creating undo/redo functionality. For example, a simple subclass that captures every state change into the undo history:
```ts
-@Injectable()
class UndoService extends UndoManager {
private skipNextChange = true;
diff --git a/projects/integration/src/app/api-tests/signal-store.spec.ts b/projects/integration/src/app/api-tests/signal-store.spec.ts
new file mode 100644
index 00000000..b911719a
--- /dev/null
+++ b/projects/integration/src/app/api-tests/signal-store.spec.ts
@@ -0,0 +1,15 @@
+import { PersistentStore, RootStore, Store } from '@s-libs/signal-store';
+
+describe('signal-store', () => {
+ it('has PersistentStore', () => {
+ expect(PersistentStore).toBeDefined();
+ });
+
+ it('has RootStore', () => {
+ expect(RootStore).toBeDefined();
+ });
+
+ it('has Store', () => {
+ expect(Store).toBeDefined();
+ });
+});
diff --git a/projects/integration/src/app/app.component.html b/projects/integration/src/app/app.component.html
index c9cf9c14..64f0094a 100644
--- a/projects/integration/src/app/app.component.html
+++ b/projects/integration/src/app/app.component.html
@@ -1,6 +1,8 @@
NasModel
-
-AppState Performance
+App State Performance
+-
+Signal Store Performance
-
Playground
diff --git a/projects/integration/src/app/app.config.ts b/projects/integration/src/app/app.config.ts
index 5305ea3b..7f5b7e4a 100644
--- a/projects/integration/src/app/app.config.ts
+++ b/projects/integration/src/app/app.config.ts
@@ -1,8 +1,7 @@
import { ApplicationConfig } from '@angular/core';
+import { provideAnimations } from '@angular/platform-browser/animations';
import { provideRouter } from '@angular/router';
-
import { routes } from './app.routes';
-import { provideAnimations } from '@angular/platform-browser/animations';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideAnimations()],
diff --git a/projects/integration/src/app/app.routes.ts b/projects/integration/src/app/app.routes.ts
index 59571e11..18da81fc 100644
--- a/projects/integration/src/app/app.routes.ts
+++ b/projects/integration/src/app/app.routes.ts
@@ -2,10 +2,15 @@ import { Routes } from '@angular/router';
import { AppStatePerformanceComponent } from './app-state-performance/app-state-performance.component';
import { NasModelComponent } from './nas-model/nas-model.component';
import { PlaygroundComponent } from './playground/playground.component';
+import { SignalStorePerformanceComponent } from './signal-store-performance/signal-store-performance.component';
export const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'nas-model' },
{ path: 'nas-model', component: NasModelComponent },
{ path: 'app-state-performance', component: AppStatePerformanceComponent },
+ {
+ path: 'signal-store-performance',
+ component: SignalStorePerformanceComponent,
+ },
{ path: 'playground', component: PlaygroundComponent },
];
diff --git a/projects/integration/src/app/signal-store-performance/deep-performance/deep-performance.component.html b/projects/integration/src/app/signal-store-performance/deep-performance/deep-performance.component.html
new file mode 100644
index 00000000..c207947a
--- /dev/null
+++ b/projects/integration/src/app/signal-store-performance/deep-performance/deep-performance.component.html
@@ -0,0 +1,6 @@
+
+ Depth
+
+ Iterations
+
+
diff --git a/projects/integration/src/app/signal-store-performance/deep-performance/deep-performance.component.ts b/projects/integration/src/app/signal-store-performance/deep-performance/deep-performance.component.ts
new file mode 100644
index 00000000..8384df96
--- /dev/null
+++ b/projects/integration/src/app/signal-store-performance/deep-performance/deep-performance.component.ts
@@ -0,0 +1,39 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ createEnvironmentInjector,
+ EnvironmentInjector,
+ inject,
+} from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { RootStore } from '@s-libs/signal-store';
+import {
+ DeepState,
+ runDeep,
+ subscribeDeep,
+} from '../../../../../signal-store/src/performance/deep-performance';
+import { unsubscribe } from '../../../../../signal-store/src/performance/performance-utils';
+
+@Component({
+ selector: 'sl-deep-performance',
+ templateUrl: './deep-performance.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+ imports: [FormsModule],
+})
+export class DeepPerformanceComponent {
+ protected depth = 1000;
+ protected iterations = 1000;
+
+ #injector = inject(EnvironmentInjector);
+
+ protected async run(): Promise {
+ // `any` because we import functions directly from `signal-store` and TS doesn't like that
+ const store: any = new RootStore(new DeepState(this.depth));
+ const injector = createEnvironmentInjector([], this.#injector);
+
+ subscribeDeep(store, injector);
+ await runDeep(store, this.iterations, async () => Promise.resolve());
+ unsubscribe(this.depth, injector);
+ }
+}
diff --git a/projects/integration/src/app/signal-store-performance/signal-store-performance.component.html b/projects/integration/src/app/signal-store-performance/signal-store-performance.component.html
new file mode 100644
index 00000000..a9760f7e
--- /dev/null
+++ b/projects/integration/src/app/signal-store-performance/signal-store-performance.component.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/projects/integration/src/app/signal-store-performance/signal-store-performance.component.scss b/projects/integration/src/app/signal-store-performance/signal-store-performance.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/projects/integration/src/app/signal-store-performance/signal-store-performance.component.ts b/projects/integration/src/app/signal-store-performance/signal-store-performance.component.ts
new file mode 100644
index 00000000..f6fb86f3
--- /dev/null
+++ b/projects/integration/src/app/signal-store-performance/signal-store-performance.component.ts
@@ -0,0 +1,13 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { DeepPerformanceComponent } from './deep-performance/deep-performance.component';
+import { WidePerformanceComponent } from './wide-performance/wide-performance.component';
+
+@Component({
+ selector: 'sl-signal-store-performance',
+ templateUrl: './signal-store-performance.component.html',
+ styleUrls: ['./signal-store-performance.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+ imports: [DeepPerformanceComponent, WidePerformanceComponent],
+})
+export class SignalStorePerformanceComponent {}
diff --git a/projects/integration/src/app/signal-store-performance/wide-performance/wide-performance.component.html b/projects/integration/src/app/signal-store-performance/wide-performance/wide-performance.component.html
new file mode 100644
index 00000000..202a9907
--- /dev/null
+++ b/projects/integration/src/app/signal-store-performance/wide-performance/wide-performance.component.html
@@ -0,0 +1,6 @@
+
+ Width
+
+ Iterations
+
+
diff --git a/projects/integration/src/app/signal-store-performance/wide-performance/wide-performance.component.ts b/projects/integration/src/app/signal-store-performance/wide-performance/wide-performance.component.ts
new file mode 100644
index 00000000..2e91ab1b
--- /dev/null
+++ b/projects/integration/src/app/signal-store-performance/wide-performance/wide-performance.component.ts
@@ -0,0 +1,39 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ createEnvironmentInjector,
+ EnvironmentInjector,
+ inject,
+} from '@angular/core';
+import { RootStore } from '@s-libs/signal-store';
+import { unsubscribe } from '../../../../../signal-store/src/performance/performance-utils';
+import {
+ runWide,
+ subscribeWide,
+ WideState,
+} from '../../../../../signal-store/src/performance/wide-performance';
+import { FormsModule } from '@angular/forms';
+
+@Component({
+ selector: 'sl-wide-performance',
+ templateUrl: './wide-performance.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+ imports: [FormsModule],
+})
+export class WidePerformanceComponent {
+ protected width = 1000;
+ protected iterations = 1000;
+
+ #injector = inject(EnvironmentInjector);
+
+ protected async run(): Promise {
+ // `any` because we import functions directly from `signal-store` and TS doesn't like that
+ const store: any = new RootStore(new WideState(this.width));
+ const injector = createEnvironmentInjector([], this.#injector);
+
+ subscribeWide(store, injector);
+ await runWide(store, this.iterations, async () => Promise.resolve());
+ unsubscribe(this.width, injector);
+ }
+}
diff --git a/projects/ng-app-state/README.md b/projects/ng-app-state/README.md
index 11071ae8..a99c03eb 100644
--- a/projects/ng-app-state/README.md
+++ b/projects/ng-app-state/README.md
@@ -1,5 +1,7 @@
Painlessly integrate [`app-state`](https://github.com/simontonsoftware/s-libs/projects/app-state) into template-driven Angular forms.
+**PLEASE NOTE:** [`signal-store`](https://github.com/simontonsoftware/s-libs/tree/master/projects/signal-store) is now available for Angular apps, based on Angular signals instead of RxJS. Its updated design does not require a separate library like this for integration into forms. Instead, with that library you simply use `[(ngModel)]="store.state"`.
+
## Installation
Install along with its peer dependencies using:
diff --git a/projects/signal-store/.eslintrc.json b/projects/signal-store/.eslintrc.json
new file mode 100644
index 00000000..4293ddca
--- /dev/null
+++ b/projects/signal-store/.eslintrc.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../.eslintrc.json",
+ "ignorePatterns": ["!**/*"],
+ "overrides": [
+ {
+ "files": ["*.ts"],
+ "parserOptions": {
+ "project": ["projects/signal-store/tsconfig.(lib|spec).json"]
+ },
+ "rules": {}
+ },
+ { "files": ["*.html"], "rules": {} }
+ ]
+}
diff --git a/projects/signal-store/README.md b/projects/signal-store/README.md
new file mode 100644
index 00000000..422b7aac
--- /dev/null
+++ b/projects/signal-store/README.md
@@ -0,0 +1,117 @@
+A state management library build on Angular signals. An API inspired by [`app-state`](https://github.com/simontonsoftware/s-libs/tree/master/projects/app-state) allows you to directly read, write and observe any part of your state without writing any selectors, actions, or reducers. Directly bind any part of the store using `[(ngModel)]`.
+
+## API Documentation
+
+Once you are familiar with the basics, definitely check out the [api documentation](https://simontonsoftware.github.io/s-libs/signal-store) to find more details and utilities.
+
+## Introduction
+
+A basic idea behind this library is to keep all the state of your app in one place, accessible for any component or service to access, modify and subscribe to changes. This has several benefits:
+
+- Components no longer need multiple inputs and outputs to route state and mutations to the proper components. Instead, they can obtain the store via dependency injection.
+- During debugging, you can look in one place to see the state of your entire app. Moreover, development tools can be used to see this information at a glance along with a full history of changes leading up to the current state ([Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en)).
+- The objects in the store are immutable (as long as you only modify the state via the store, as you should), which enables more benefits:
+ - Immutable objects allow you to use on-push change detection, which can be a huge performance gain for apps with a large state.
+ - Undo/redo features become very simple. Check the API docs for a helper.
+- Every piece of state is observable. You can subscribe to the root of the store to get notified of every state change anywhere in the app, for a specific boolean buried deep within your state, or anywhere in between.
+
+2 terms are worth defining immediately. As they are used in this library, they mean:
+
+- **State**: a javascript object (or primitive) kept within the store. A subset of the entire application state is still considered state on its own.
+- **Store**: the keeper of state. You will always interact with the state via the store, whether to access it, observe it or modify it. You can obtain store objects to represent a subset of your state as well, which are also store objects on their own.
+
+## Installation
+
+Install along with its peer dependencies using:
+
+```shell script
+npm install @s-libs/signal-store @s-libs/js-core @s-libs/micro-dash
+```
+
+## Setup
+
+Define the shape of your application state using typescript classes or interfaces (but prefer classes, as noted in the style guide below). For example:
+
+```ts
+// state/my-state.ts
+
+import { User } from "./user";
+
+export class MyState {
+ loading = true;
+ currentUser?: User;
+}
+```
+
+```ts
+// state/user.ts
+
+export class User {
+ id: string;
+ name: string;
+}
+```
+
+Then create a subclass of `RootStore`. A single instance of that class will serve as the entry point to obtain and modify the state it holds. Most often you will make that class an Angular service that can be injected anywhere in your app. For example:
+
+```ts
+// state/my-store.ts
+
+import { Injectable } from "@angular/core";
+import { RootStore } from "@s-libs/signal-store";
+import { MyState } from "./my-state";
+
+@Injectable({ providedIn: "root" })
+export class MyStore extends RootStore {
+ constructor() {
+ super(new MyState());
+ }
+}
+```
+
+## Usage
+
+Consider this translation of the counter example from the `ngrx/store` readme:
+
+```ts
+// app-state.ts
+class AppState {
+ counter = 0;
+}
+
+// app-store.ts
+@Injectable({ providedIn: "root" })
+class AppStore extends RootStore {
+ constructor() {
+ super(new AppState());
+ }
+}
+
+// my-app-component.ts
+@Component({
+ selector: "sl-my-app",
+ template: `
+
+