@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

NamespacePurposeFunction
state.*Application stateusePath
derived.*Computed projectionsusePath
intent.*UI signalsuseIntent

vs NgRx

ConcernNgRxEveryState
DispatchActions (file)useIntent
UpdateReducers (file)Subscribers
Side effectsEffects + RxJSSubscribers
ReadSelectors (file)usePath
AsyncEffects + actionsuseAsync

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.