A state management library build on Angular signals. An API inspired by 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)].
Once you are familiar with the basics, definitely check out the api documentation to find more details and utilities.
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:
2 terms are worth defining immediately. As they are used in this library, they mean:
Install along with its peer dependencies using:
npm install @s-libs/signal-store @s-libs/js-core @s-libs/micro-dash
Define the shape of your application state using typescript classes or interfaces (but prefer classes, as noted in the style guide below). For example:
// state/my-state.ts
import { User } from "./user";
export class MyState {
loading = true;
currentUser?: User;
}
// 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:
// 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<MyState> {
constructor() {
super(new MyState());
}
}
Consider this translation of the counter example from the ngrx/store readme:
// app-state.ts
class AppState {
counter = 0;
}
// app-store.ts
@Injectable({ providedIn: "root" })
class AppStore extends RootStore<AppState> {
constructor() {
super(new AppState());
}
}
// my-app-component.ts
@Component({
selector: "sl-my-app",
template: `
<button (click)="counter.state = counter.state + 1">Increment</button>
<div>Current Count: {{ counter.state }}</div>
<button (click)="counter.state = counter.state - 1">Decrement</button>
<button (click)="counter.state = 0">Reset Counter</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class MyAppComponent {
protected counter = inject(AppStore)("counter");
}
new StateObject() come with the default values for all its properties..state at the end of the chain. E.g.:store("currentUser")("name").state; // do this
store.state.currentUser.name; // not this
This allows usage inside templates and effects to be marked dirty less often. When using .state any change at the level of the store or lower will mark it dirty, so delaying the use of .state until a leaf node in your state will trigger it less often.