Metronome

Angular Signals: A New Mental Model for Reactivity, Not Just a New API

Sonu Kapoor
Sonu Kapoor
Published in
·Updated:

Share this article

Angular Signals: A New Mental Model for Reactivity, Not Just a New API
SitePoint Premium
Stay Relevant and Grow Your Career in Tech
  • Premium Results
  • Publish articles on SitePoint
  • Daily curated jobs
  • Learning Paths
  • Discounts to dev tools

7 Day Free Trial. Cancel Anytime.

Angular Signals are not just another feature. They represent a different way to think about data flow. If you’re coming from RxJS or standard @Input() / @Output() bindings, you might think of Signals as a simpler syntax for observable values. However, that’s like saying a violin is just a smaller cello. The shape of the instrument affects the kind of music you create. 

In this article, I won’t repeat the documentation or guide you through another counter example. Instead, I’ll introduce a new way of thinking that Signals enable, along with some real challenges I faced when using them in production.

Signals as Reactive Variables, Not Streams

Think of Signals as reactive variables, not data streams. This is the key change in perspective. In RxJS, we typically push values down a stream, combine them, and respond with side effects through subscribe(). Signals turn this concept around. You read from them like variables. Angular automatically tracks dependencies and triggers reactions.

Here’s the best way I explain Signals in code:

const firstName = signal('John');

const lastName = signal('Doe');

const fullName = computed(() => `${firstName()} ${lastName()}`);

In this example, fullName is automatically recalculated when either firstName or lastName changes. You don’t need to think in terms of map, combineLatest, or teardown logic. You just declare relationships.

If this feels like Vue or SolidJS, it’s not a coincidence.

Gotcha #1: Implicit Dependencies Can Backfire

When you read from a Signal inside a computed() or effect(), Angular tracks that read as a dependency. But this can go wrong fast when you’re not aware of those reads.

let counter = signal(0);

const doubled = computed(() => {

  console.log('Recomputing...');

  return counter() * 2;

});

You might expect this to run only when the counter changes, but if you accidentally read another Signal inside the same function (e.g. a logging flag), it becomes a dependency too. Suddenly, toggling a debug mode flag starts recalculating your math logic.

Tip: Keep computed and effect logic narrow and deterministic. Otherwise, you’ll have phantom updates you can’t debug.

Signals vs. RxJS: Where Signals Shine – and Where They Don’t

Let’s be clear: Signals don’t replace RxJS. They’re designed to work alongside it. But understanding when to use each is critical.

Use CasePrefer SignalsPrefer RxJS
Local component stateX
Derived UI dataX
Event streams (e.g. user typing)X
Shared state across modules (via service Signals)✓ 
Complex async flows with retriesX

Signals excel at modelling value over time. RxJS excels at modelling events over time.

Gotcha #2: Computed Signals Don’t Cache Like You Think

A surprising thing I discovered: computed() doesn’t memoize like React’s useMemo() or even like you might expect from a getter.

Each time you read from a computed() signal, the logic re-runs if its inputs have changed. But if you’re calling it multiple times in a template (say in *ngIf and again in {{ }}), you may pay the cost more than once.

Tip: If a computation is expensive, consider storing it in a local const in the component class and just referencing that in the template. Or wrap it in another signal.

Rethinking State Shape: Signals Love Flat, Not Deep

In classic Angular with services and RxJS, it’s common to model state like this:

const state$ = new BehaviorSubject({

  user: null,

  settings: {},

  isLoading: false

});

In Signals, deeply nested reactive objects are awkward. You can’t say user().settings().theme() – that’s a read on a read on a read. Instead, you’ll want to flatten:

const user = signal<User | null>(null);

const settings = signal<Settings>({});

Tip: Model each piece of state with its own signal. You’ll gain flexibility and easier reactivity control.

Practical Scenario: Form Label Customization

Let’s say you have a SearchSidebarComponent, and you want to customize its labels from a parent. Here’s the naive way:

@Input() labels = signal<MapSidebarLabels>();

What happens if you try to derive a computed value?

labelsFinal = computed(() => {

  const raw = this.labels();

  return { ...raw, title: raw.title.toUpperCase() };

});

Now, suppose in the parent you write:

<search-sidebar [labels]="labelsFinal()" />

This works, but you’re calling a signal within a signal. And if you change your template to <search-sidebar [labels]="labelsFinal" />, it fails with a type mismatch.

Tip: Angular’s input system isn’t yet fully signal-native. Until it is, flatten the value before passing inputs.

Gotcha #3: effect() Runs Immediately – And Maybe Again

Unlike RxJS subscribe(), which fires only when something emits, effect() fires once on creation, even if the signal hasn’t changed yet.

effect(() => {

  console.log("API call for", userId());

});

This will run once immediately, even if userId hasn’t changed yet. Be careful when placing side effects like HTTP calls or analytics tracking inside effect().

Tip: Guard effect() logic with null checks or early returns if needed.

Final Thoughts

Signals in Angular aren’t just a new syntax, they’re a shift in mental model. Once you stop thinking in observables and start thinking in variables that react, you’ll find your components are smaller, faster, and easier to reason about.

But like any new tool, Signals come with sharp edges. Know the tradeoffs, learn the patterns, and above all, don’t treat Signals like fancy getters. They’re much more powerful than that, but only if you understand the model.

Sonu Kapoor is a Google Developer Expert (GDE) in Angular, a Microsoft MVP, and a long-time contributor to the frontend ecosystem. With 20+ years in web development - from the early days of IE6 and ActiveX to today’s modern frameworks - he has authored two technical books and contributed to the Angular framework itself. He regularly writes for developer platforms and speaks at global tech conferences.

angular

Share this article

Subscribe to our newsletter

Get the freshest news and resources for developers, designers and digital creators in your inbox each week

© 2000 – 2025 SitePoint Pty. Ltd.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.