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.

TS 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);
}
TS 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');
  }
}
TS 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 = ''; }
}
TS 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');
  }
}
TS 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');
  }
}
TS 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.

JS @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

TS 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);
});