DEV Community

Cover image for Side Effects: Angular Signals vs rs-x
Robert Sanders
Robert Sanders

Posted on

Side Effects: Angular Signals vs rs-x

The Problem

You want to react to state changes and run some code as a side effect.

Every reactive library solves this. But how they solve it changes a lot about your code.

Angular Signals — effect()

effect(() => {
  console.log('price changed:', price());
});
Enter fullscreen mode Exit fullscreen mode

✅ Simple to read

⚠️ Re-runs when any signal read inside changes

⚠️ Requires Angular's runtime and compiler

⚠️ Lifecycle managed separately from your data

rs-x — The Sequence Expression

const expr = rsx(
  '(trackPrice(price), trackQty(quantity), price * quantity)'
)(model);
Enter fullscreen mode Exit fullscreen mode

(a, b, c) is standard JavaScript.

The comma operator evaluates left to right and returns the last value.

rs-x parses it exactly as a JS engine would — no new syntax, no compiler.

Fine-Grained by Default

rsx('(trackPrice(price), trackQty(quantity), price * quantity)')(model)
//       ↑ tracks price       ↑ tracks quantity        ↑ return value
Enter fullscreen mode Exit fullscreen mode
Change Re-runs
model.price = 150 trackPrice only
model.quantity = 5 trackQty only
Both change Both re-run

Each segment tracks its own dependencies — independently.

Conditional Side Effects

rsx('(count > limit && onOverflow(count), count)')(model)
Enter fullscreen mode Exit fullscreen mode

&& short-circuits — standard JavaScript behaviour.

onOverflow is only called when the guard is truthy.

count is still tracked as the return value regardless.

Lifecycle — Zero Ceremony

const expr = rsx('(notify(status), status)')(model);

// Later...
expr.dispose(); // ← every side effect cleaned up automatically
Enter fullscreen mode Exit fullscreen mode
Angular Signals rs-x
Register effect(() => {...}) part of the expression string
Cleanup DestroyRef / manual expr.dispose()
Scope Angular component the expression itself
Syntax Framework API standard JavaScript

Cross-Property Tracking

// trackView watches userId
// formatName watches profile.name
// Each is independent

rsx('(trackView(userId), formatName(profile.name))')(model)
Enter fullscreen mode Exit fullscreen mode

Change userIdtrackView re-runs, formatName does not

Change profile.nameformatName re-runs, trackView does not

The Philosophy

No new API surface. No compiler. Just JavaScript expressions, tracked reactively.

Side effects are co-located with the data they read.

Their lifecycle is owned by the expression, not your application code.

When the expression is disposed — the side effects go with it.

Learn More

📖 Side effects deep divehttps://rsxjs.com/docs/core-concepts/side-effects

▶️ Live playgroundhttps://rsxjs.com/playground

rs-x on GitHub → github.com/robert-sanders-software-ontwikkeling/rs-x

Top comments (0)