EveryState + Angular 19
A complete task manager. Every file shown below.
Install: npm i @everystate/core @everystate/angular
6 files, ~160 lines
Store service, 4 components, bootstrap.
Zero logic in components
Read with usePath, publish with useIntent.
No @Input/@Output
Clean templates via store.
store.service.ts Store + Logic
import { Injectable } from '@angular/core';
import { createEveryState } from '@everystate/core';
export interface Task { id: string; text: string; completed: boolean; important: boolean; }
@Injectable({ providedIn: 'root' })
export class StoreService {
private store: any;
constructor() {
this.store = createEveryState({
state: { tasks: [], taskCount: 0, filter: 'all' },
derived: { tasks: { filtered: [] } }
});
// Intent handlers - business logic lives here, not in components
this.store.subscribe('intent.addTask', (text: string) => {
const t = String(text || '').trim();
if (!t) return;
const tasks: Task[] = this.store.get('state.tasks') || [];
const next = [...tasks, { id: Date.now().toString(36), text: t, completed: false, important: /!/i.test(t) }];
this.store.set('state.tasks', next);
this.store.set('state.taskCount', next.length);
});
this.store.subscribe('intent.toggleTask', (id: string) => {
const tasks: Task[] = this.store.get('state.tasks') || [];
this.store.set('state.tasks', tasks.map(x => x.id === id ? { ...x, completed: !x.completed } : x));
});
this.store.subscribe('intent.deleteTask', (id: string) => {
const tasks: Task[] = this.store.get('state.tasks') || [];
const next = tasks.filter(x => x.id !== id);
this.store.set('state.tasks', next);
this.store.set('state.taskCount', next.length);
});
this.store.subscribe('intent.changeFilter', (f: string) => this.store.set('state.filter', f));
// Derived state
const recompute = () => {
const tasks: Task[] = this.store.get('state.tasks') || [];
const f = this.store.get('state.filter');
this.store.set('derived.tasks.filtered',
f === 'active' ? tasks.filter(t => !t.completed)
: f === 'completed' ? tasks.filter(t => t.completed)
: f === 'important' ? tasks.filter(t => t.important) : tasks);
};
this.store.subscribe('state.tasks', recompute);
this.store.subscribe('state.filter', recompute);
recompute();
}
get = (p: string) => this.store.get(p);
set = (p: string, v: any) => this.store.set(p, v);
subscribe = (p: string, h: Function) => this.store.subscribe(p, h);
}
header.component.ts Read-only
import { Component, Signal } from '@angular/core';
import { usePath } from '@everystate/angular';
import { StoreService } from '../store.service';
@Component({
selector: 'app-header', standalone: true,
template: `<header>
<div class="title">Angular + EveryState</div>
<div class="badge">{{ count() }} task{{ count() !== 1 ? 's' : '' }}</div>
</header>`
})
export class HeaderComponent {
count: Signal<number>;
constructor(private store: StoreService) {
this.count = usePath<number>(store, 'state.taskCount');
}
}
task-input.component.ts Local + intent
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { useIntent } from '@everystate/angular';
import { StoreService } from '../store.service';
@Component({
selector: 'app-task-input', standalone: true, imports: [FormsModule],
template: `<div class="card"><div class="input-row">
<input [(ngModel)]="text" placeholder="What needs to be done?" (keydown.enter)="addTask()" />
<button class="primary" (click)="addTask()">Add</button>
</div></div>`
})
export class TaskInputComponent {
text = '';
private add: (text: string) => void;
constructor(private store: StoreService) { this.add = useIntent(store, 'intent.addTask'); }
addTask(): void { this.add(this.text); this.text = ''; }
}
filters.component.ts Read + publish
import { Component, Signal } from '@angular/core';
import { usePath, useIntent } from '@everystate/angular';
import { StoreService } from '../store.service';
@Component({
selector: 'app-filters', standalone: true,
template: `<div class="card filters">
<button [class.active]="filter() === 'all'" (click)="setFilter('all')">All</button>
<button [class.active]="filter() === 'active'" (click)="setFilter('active')">Active</button>
<button [class.active]="filter() === 'completed'" (click)="setFilter('completed')">Done</button>
<button [class.active]="filter() === 'important'" (click)="setFilter('important')">Important</button>
</div>`
})
export class FiltersComponent {
filter: Signal<string>;
setFilter: (f: string) => void;
constructor(private store: StoreService) {
this.filter = usePath<string>(store, 'state.filter');
this.setFilter = useIntent(store, 'intent.changeFilter');
}
}
task-list.component.ts Derived + intents
import { Component, Signal } from '@angular/core';
import { usePath, useIntent } from '@everystate/angular';
import { StoreService, Task } from '../store.service';
@Component({
selector: 'app-task-list', standalone: true,
template: `<div class="card"><ul>
@for (task of items(); track task.id) {
<li>
<input type="checkbox" [checked]="task.completed" (change)="toggle(task.id)" />
<span>{{ task.text }}</span>
<div class="right">
@if (task.important) { <span class="pill">Important</span> }
<button (click)="del(task.id)">Delete</button>
</div>
</li>
}
</ul></div>`
})
export class TaskListComponent {
items: Signal<Task[]>;
toggle: (id: string) => void;
del: (id: string) => void;
constructor(private store: StoreService) {
this.items = usePath<Task[]>(store, 'derived.tasks.filtered');
this.toggle = useIntent(store, 'intent.toggleTask');
this.del = useIntent(store, 'intent.deleteTask');
}
}
app.component.ts Composition
import { Component } from '@angular/core';
import { HeaderComponent } from './header/header.component';
import { FiltersComponent } from './filters/filters.component';
import { TaskInputComponent } from './task-input/task-input.component';
import { TaskListComponent } from './task-list/task-list.component';
@Component({
selector: 'app-root', standalone: true,
imports: [HeaderComponent, FiltersComponent, TaskInputComponent, TaskListComponent],
template: `<div class="app">
<app-header />
<div class="container">
<app-filters />
<app-task-input />
<app-task-list />
</div>
</div>`
})
export class AppComponent {}
The Adapter Source
This is the entire @everystate/angular package. ~30 lines.
@everystate/angular/index.js ~30 lines
import { signal } from '@angular/core';
export function usePath(store, path) {
const sig = signal(store.get(path));
store.subscribe(path, (val) => sig.set(val));
return sig.asReadonly();
}
export function useIntent(store, path) {
return (value) => store.set(path, value);
}
export function useWildcard(store, wildcardPath) {
const parentPath = wildcardPath.endsWith('.*') ? wildcardPath.slice(0, -2) : wildcardPath;
const sig = signal(store.get(parentPath));
store.subscribe(wildcardPath, () => sig.set(store.get(parentPath)));
return sig.asReadonly();
}
export function useAsync(store, path) {
return {
data: usePath(store, path + '.data'),
status: usePath(store, path + '.status'),
error: usePath(store, path + '.error'),
execute: (fetcher) => store.setAsync(path, fetcher),
cancel: () => store.cancel(path),
};
}
Testing Without Angular
store.spec.ts No TestBed
import { StoreService } from './store.service';
test('adding a task increments taskCount', () => {
const svc = new StoreService();
svc.set('intent.addTask', 'Buy groceries');
expect(svc.get('state.taskCount')).toBe(1);
expect(svc.get('state.tasks')[0].text).toBe('Buy groceries');
});
test('filter shows only active tasks', () => {
const svc = new StoreService();
svc.set('intent.addTask', 'task 1');
svc.set('intent.addTask', 'task 2');
svc.set('intent.toggleTask', svc.get('state.tasks')[0].id);
svc.set('intent.changeFilter', 'active');
expect(svc.get('derived.tasks.filtered')).toHaveLength(1);
});