Angular Signals in Practice — A Scientific, Production‑Minded Guide (2026)
Abstract
Angular Signals are no longer an experimental abstraction or a syntactic convenience — they are the foundational reactive primitive of modern Angular. This document presents a scientific, production‑oriented analysis of Signals, grounded in empirical comparison with RxJS, architectural reasoning, and real‑world use cases.
Rather than evangelizing Signals, we evaluate them.
We observe behavior, isolate variables, compare reactive models, and derive actionable conclusions for engineers building large‑scale Angular applications.
Why Signals Exist (Historical Context)
Angular’s original reactivity model was built around:
- Zone.js
- Global change detection
- Imperative mutation + dirty checking
RxJS filled critical gaps:
- Asynchronous streams
- Event composition
- Cancellation semantics
However, RxJS was never designed to be a UI state primitive. Over time, it became one by necessity, not intent.
Signals address this mismatch.
They are:
- Synchronous
- Deterministic
- Dependency‑tracked
- Pull‑based
This makes them ideal for state, not streams.
Mental Model: Signals vs Observables
| Dimension | Signals | RxJS |
|---|---|---|
| Execution | Pull | Push |
| Timing | Synchronous | Async |
| Dependency Tracking | Automatic | Manual |
| UI State | Native | Incidental |
| Cancellation | ❌ | ✅ |
| Composition | computed | pipe |
| Lifecycle | Scoped | Subscription‑based |
Key Insight: Signals are state machines. RxJS is event algebra.
Case Study: Idle User Logout (Insurance Domain)
Problem Definition
- Users must be logged out after inactivity
- A warning modal appears before logout
- UI updates continuously as time elapses
This is derived UI state, not async orchestration.
RxJS Baseline (BehaviorSubject)
private idleTimeRemaining$ = new BehaviorSubject<number | null>(null);
idleLabel$ = this.idleTimeRemaining$.pipe(
filter(Boolean),
map(t => t! > 60 ? `${Math.ceil(t!/60)} minutes` : `${t} seconds`)
);
Observations
- Requires manual subscription management
- UI consumes derived async state
- Implicit lifecycle coupling
- Overhead disproportionate to complexity
Signal‑Based Refactor
private idleTime = signal<number | null>(null);
idleLabel = computed(() => {
const t = idleTime();
if (t === null) return '';
return t > 60 ? `${Math.ceil(t/60)} minutes` : `${t} seconds`;
});
Observations
- No subscriptions
- Pure derivation
- Deterministic recomputation
- Explicit state ownership
Scientific Comparison
Variable Isolation
- Same input events
- Same UI
- Same business logic
Only reactivity model changed.
Result
Signals reduced:
- Cognitive load
- Boilerplate
- Failure modes
Without sacrificing correctness.
Signals as Deterministic State Machines
A signal represents:
A value + a dependency graph + a recomputation contract
Properties:
- No hidden side effects
- Referential transparency (when used correctly)
- Cache invalidation handled by runtime
This enables local reasoning — a critical property in large systems.
Computed Signals: Rules of Physics
Computed signals must obey:
- Purity — no side effects
- Determinism — same inputs → same output
- No async — defer async to RxJS
Violating these rules breaks the model.
Use effect() for:
- Logging
- Persistence
- DOM escape hatches
Dynamic Dependency Tracking
Signals track dependencies per execution, not per declaration.
const display = computed(() => {
if (expanded()) return details();
return summary();
});
This enables:
- Conditional reactivity
- Efficient recomputation
- Fine‑grained invalidation
Mutation Rules (Arrays & Objects)
Signals rely on structural change.
❌ Mutating objects does not trigger updates.
user().age = 31; // ❌
✅ Replace immutably.
user.set({ ...user(), age: 31 });
This enforces architectural discipline.
Equality Semantics
Signals use reference equality by default.
You may override equality for domain objects:
signal(user, { equal: (a,b) => a.id === b.id });
Use sparingly. Incorrect equality functions cause stale state.
Effects: Controlled Side Effects
effect(() => {
saveToStorage(state());
});
Rules:
- Never write to the same signal inside an effect
- Avoid circular dependencies
- Prefer effects in services
Signals + RxJS: A Truce
Signals do not replace RxJS.
Use RxJS for:
- HTTP
- WebSockets
- User events
- Cancellation
- Complex async workflows
Bridge with:
toSignal()toObservable()rxResource()
This is the modern Angular stack.
Production Architecture Guidance
Use Signals For
- Component state
- View models
- Derived UI
- Feature‑local state
Use RxJS For
- Data fetching
- Side effects
- Streams
- Inter‑process communication
Anti‑Patterns
- Signals for HTTP
- Effects for derivation
- Observables for toggles
Why This Matters in 2025+
Angular is moving toward:
- Zoneless execution
- Deterministic rendering
- Fine‑grained invalidation
Signals are the cornerstone.
Ignoring them means fighting the framework.
Conclusion
Signals are not syntactic sugar.
They are a new execution model.
Used correctly, they:
- Reduce bugs
- Improve performance
- Clarify intent
- Scale better
RxJS remains essential — but no longer carries the burden of UI state.
This separation of concerns is Angular’s biggest architectural leap since Ivy.
✍️ Cristian Sifuentes
Full‑stack engineer • Angular & Reactive Systems

Top comments (0)