pyreon

If you've used Angular signals (v16+), you already know Pyreon's reactivity — the signal / computed / effect API is nearly identical. What changes is the surrounding model: Angular's class components, decorators, HTML templates, dependency injection, and RxJS become Pyreon's function components, JSX, signals, and context. (There's no Angular compat layer — this is a rewrite — but the reactive core is the closest match of any framework.)

Reactivity: almost the same API

// Angular
count = signal(0)
count()              // read
count.set(1)         // write
count.update((c) => c + 1)
doubled = computed(() => count() * 2)
effect(() => console.log(count()))
// Pyreon — read/write/update/computed/effect are the same shapes
const count = signal(0)
count()              // read
count.set(1)         // write
count.update((c) => c + 1)
const doubled = computed(() => count() * 2)
effect(() => console.log(count()))

The signal muscle memory transfers directly. The difference is where they live: in Angular they're class fields; in Pyreon they're consts in a function component body (which runs once).

API map

AngularPyreonNotes
signal(v)signal(v)same: s(), s.set(), s.update()
computed(fn)computed(fn)same
effect(fn)effect(fn)same auto-tracked effect
@Component classfunction componentfunction Foo(props) { return <…/> }
@Input()props.xread in a reactive scope
@Output() / EventEmittercallback propprops.onChange?.(value)
ngOnInitonMountruns after mount
ngOnDestroyonUnmount / cleanup returnreturn cleanup from onMount
services + DI@pyreon/store / contextsingleton stores or provide/useContext
RxJS streamssignals / @pyreon/rx@pyreon/rx is signal-aware (filter/map/debounce…)
Angular Router@pyreon/routerloaders, guards, typed search params
Angular Universal@pyreon/zerofs-routing, SSR/SSG/ISR, adapters

Templates → JSX

<!-- Angular -->
<button [class.active]="isOn()" (click)="toggle()">{{ label() }}</button>
<li *ngFor="let row of rows()">{{ row.label }}</li>
<p *ngIf="show()">Hi</p>
// Pyreon
<button class={{ active: isOn() }} onClick={toggle}>{() => label()}</button>
<For each={rows} by={(row) => row.id}>{(row) => <li>{row.label}</li>}</For>
{() => (show() ? <p>Hi</p> : null)}   // or <Show when={show}><p>Hi</p></Show>

{{ x() }}{() => x()}; [prop]prop={...}; (event)onEvent; *ngIf<Show> / ternary; *ngFor → keyed <For>; [(ngModel)]value={s} onInput={(e) => s.set(e.currentTarget.value)}.

A side-by-side

// Angular
@Component({ template: `<button (click)="inc()">{{ count() }} / {{ doubled() }}</button>` })
class Counter {
  count = signal(0)
  doubled = computed(() => this.count() * 2)
  inc() { this.count.update((c) => c + 1) }
}
// Pyreon
function Counter() {
  const count = signal(0)
  const doubled = computed(() => count() * 2)
  return <button onClick={() => count.update((c) => c + 1)}>{() => `${count()} / ${doubled()}`}</button>
}

RxJS

Much of what RxJS does for synchronous UI state, signals + computed + effect do directly. For stream-like transforms over reactive collections (filter, map, debounce, throttle, groupBy), @pyreon/rx is the signal-aware equivalent — its operators take a signal and return a Computed.

Cheat sheet

  • signal / computed / effect — identical API (read s(), write s.set() / s.update())

  • @Component class → function component; @Inputprops; @Output → callback prop

  • ngOnInitonMount; ngOnDestroyonUnmount; services/DI → @pyreon/store / context

  • *ngIf<Show>; *ngFor<For each={s} by={...}>; [(ngModel)]value+onInput

  • RxJS → signals + @pyreon/rx; Angular Router → @pyreon/router; Universal → @pyreon/zero

Angular signal users get the closest reactivity match of any framework; the work is converting classes + HTML templates into function components + JSX.

Coming from Angular