@everystate/angular 1.0.0
Angular adapter for @everystate/core. Four functions - the entire API. ~30 lines.
See a complete task manager: Angular Example
Install
npm install @everystate/angular @everystate/core
Peers: @angular/core >=17.0.0, @everystate/core >=1.0.5.
Quick Start
// store.service.ts
@Injectable({ providedIn: 'root' })
export class StoreService {
private store = createEveryState({ state: { count: 0 } });
get = this.store.get.bind(this.store);
set = this.store.set.bind(this.store);
subscribe = this.store.subscribe.bind(this.store);
constructor() {
this.store.subscribe('intent.increment', () => {
this.store.set('state.count', this.store.get('state.count') + 1);
});
}
}
// counter.component.ts
@Component({
selector: 'app-counter', standalone: true,
template: `<button (click)="inc(true)">{{ count() }}</button>`
})
export class CounterComponent {
count = usePath<number>(this.store, 'state.count');
inc = useIntent(this.store, 'intent.increment');
constructor(private store: StoreService) {}
}
usePath(store, path)
usePath<T>(store, path) → Signal<T>
Read-only Angular signal. Updates when store value changes. No Zone.js overhead.
this.count = usePath<number>(store, 'state.taskCount');
this.items = usePath<Task[]>(store, 'derived.tasks.filtered');
useIntent(store, path)
useIntent(store, path) → (value) → any
Stable setter function. Publishes intent to the store.
this.add = useIntent(store, 'intent.addTask');
this.toggle = useIntent(store, 'intent.toggleTask');
useWildcard(store, path)
useWildcard<T>(store, wildcardPath) → Signal<T>
Re-renders when any child of the path changes.
this.user = useWildcard(store, 'state.user.*');
useAsync(store, path)
useAsync(store, path) → { data, status, error, execute, cancel }
Async lifecycle with auto-abort.
const { data, status, execute } = useAsync(store, 'users');
execute((signal) => fetch('/api/users', { signal }).then(r => r.json()));
StoreService Pattern
The adapter provides functions, not a service. You create the service with Angular DI. Business logic lives in subscribers:
this.store.subscribe('intent.addTask', (text) => {
const tasks = this.store.get('state.tasks') || [];
const next = [...tasks, { id: genId(), text, completed: false }];
this.store.set('state.tasks', next);
});
Three Namespaces
| Namespace | Purpose | Function |
|---|---|---|
state.* | Application state | usePath |
derived.* | Computed projections | usePath |
intent.* | UI signals | useIntent |
vs NgRx
| Concern | NgRx | EveryState |
|---|---|---|
| Dispatch | Actions (file) | useIntent |
| Update | Reducers (file) | Subscribers |
| Side effects | Effects + RxJS | Subscribers |
| Read | Selectors (file) | usePath |
| Async | Effects + actions | useAsync |
Testing Without Angular
test('add task increments count', () => {
const svc = new StoreService();
svc.set('intent.addTask', 'test');
expect(svc.get('state.taskCount')).toBe(1);
});
No TestBed. No fakeAsync. Just state in, state out.