When Frameworks Meet Reactivity: Angular, Vue, and React’s Choices (4/4)

Claire ChengClaire Cheng
9 min read

Today, the three major frontend frameworks approach reactivity with distinct philosophies: Angular and Vue have embraced Signal-based architectures, while React maintains a component-centric update model. While React's name might suggest otherwise, each framework's approach to reactivity reflects their core design philosophy—like three architects designing the same building with completely different blueprints.

When Angular introduced Signals in Angular 16 (May 2023), they tackled a core frontend challenge: balancing predictability and performance in complex apps. Vue 3's ref and reactive share a similar goal, though with a different path. Then there's React—despite the name, it follows a different reactivity model. It's more like a thoughtful painter, redrawing the whole scene through a careful diff, frame by frame.

What Drives Each Framework?

Each framework's approach to reactivity comes from its core philosophy:

Angular is built for stability and scale. In the past, it relied on Zone.js and Observables, which offered flexibility but came with complexity. Signals bring a more focused and transparent model—updates are tied directly to what changes, making data flow easier to follow and optimize.

Vue puts developer experience first. Its Proxy-based reactivity feels natural to use and works well out of the box. While it handles many things for you, understanding the internals can take a bit more digging—especially with deeply nested data.

React is based on component re-rendering and a clear one-way data flow. Its model keeps things consistent and easy to reason about for many developers. React doesn't use Signals today, not because it can't, but because it follows a different mental model that works well in its own ecosystem.

The TC39 Signals Proposal and Design Paths

The TC39 Signals proposal is an initiative to define a common foundation for reactive state management in JavaScript. Initially inspired by SolidJS, it has grown into a collaborative effort with input from teams behind Angular, Vue, Svelte, and others. The aim is to establish a standard that enables consistent and efficient reactivity across different frameworks.

At its core, a signal represents a piece of state that can notify its dependents when it changes. This mechanism allows for precise updates, ensuring that only the necessary parts of an application respond to state changes. This approach allows frameworks to build their own reactive systems on top of a common model, promoting interoperability and consistency across the JavaScript ecosystem.

The proposal is still in its early stages but reflects a shared goal of unifying reactivity models in JavaScript. By establishing a common standard, developers can expect more predictable and maintainable applications.

Angular Signals: A Functional, All-In Approach

Angular built its Signal system from scratch for control and consistency:

Single API: signal() handles all data types—primitives (numbers, strings) or objects. It's a universal toolbox for any task.

Custom Dependency Tracking: Using activeConsumer and a bidirectional graph, Angular tracks dependencies precisely. When signal.get() is called, the consumer (effect or template) is registered, like a tightly coordinated conference call.

Lazy by Default: Signals track dependencies only when read, and computed() recalculates only when needed, boosting performance. Features like inject(), Signals, and typed forms show Angular's functional future

Signal updates are immutable, using functional APIs:

set: Overwrites the value, changing the reference, like replacing a notebook. Example:

const user = signal({ name: 'Alice' });
user.set({ name: 'Bob' }); // New reference

update: Computes a new value, typically changing the reference for immutability. Example:

user.update(current => ({ ...current, name: 'Charlie' })); // New reference

Note: Mutating the object in update (not recommended) preserves the reference. Always use new objects (e.g., ...) for immutability.

How Angular Signals Actually Work

Angular introduced signals in version 16 to bring more accurate and efficient reactivity. Unlike the traditional change detection that used Zone.js, signals directly track connections between state and views, so updates happen only where needed.

Here's how it works:

  1. Creating a signal sets up a data node that holds its value and its dependents.

  2. Reading the signal (like calling count()) registers an active consumer (such as an effect or computed) to that signal.

  3. Updating the signal (for example, count.set(1)) notifies all linked consumers.

This builds a clear dependency graph where signals push updates, and consumers react only when necessary. Angular uses a global activeConsumer during effect or computed evaluation to manage these links. It refreshes this graph each time to keep it accurate and clean. Signals run synchronously and only recalculate when accessed, improving performance and avoiding unnecessary checks.

By focusing on precise state-to-view relationships, Angular's signal system offers a more streamlined and predictable way to handle reactivity.

Why Angular Chose Custom Implementation Over Proxies

When Angular introduced Signals, they had a choice to make: follow the Proxy-based model used in frameworks like Vue, or build something more explicit?

They went with the latter—for a few good reasons.

First, performance and control. Proxies work by intercepting all operations on an object, even when you’re doing something simple like reading a property or calling Object.keys(). Angular took a more focused approach: only track dependencies when the developer explicitly reads a signal. Precise control over what’s reactive and when.

Then there’s predictability. With proxies, it’s sometimes hard to know exactly when reactivity is triggered, especially in complex scenarios. Angular’s signals use functions like count() and count.set(), so it’s always clear what’s happening. You can trace reactivity just by reading the code.

TypeScript support was another major reason. Signals integrate cleanly with the type system—no need for workarounds. When you use Signals in Angular, TypeScript knows exactly what type each signal holds. This means you get accurate autocomplete, type checking, and error detection.

In short, Angular focused on reactivity that’s easy to understand and reason about. Signals offer clear, explicit control over how data flows—so you always know what triggers a change, and why.

Vue's Proxy-Powered Reactivity

Dual APIs: ref for primitives, reactive for objects and arrays, like separate bags for small and large items.

Proxy-Based Tracking: Proxies intercept reads and writes, auto-tracking dependencies. Reading ref.value or a reactive property registers the consumer. Changes trigger instant notifications.

Eager by Default: ref and reactive notify consumers immediately, like a mail carrier. But computed is lazy, recalculating only when read.

Vue's updates are object-oriented, mutating values:

const count = ref(0);
count.value = 5; // Updates and notifies

const user = reactive({ name: 'Alice' });
user.name = 'Bob'; // Updates and notifies

This mutable approach is intuitive but needs side-effect management. Proxies only work for objects, so primitives use ref.

React: A Different Reactivity Model

React's useState may look similar to signal() in its functional style:

const [count, setCount] = useState(0);
setCount(5); // Feels like signal.set()

But it represents a fundamentally different approach. React's model:

No Dependency Tracking: useState updates trigger a component re-run, generating a new Virtual DOM for diffing. It's like redrawing a painting and making small changes to the parts that are different.

Performance Trade-Offs: This approach prioritizes simplicity and predictability. useMemo and useCallback provide optimization tools, though they require manual dependency management.

Philosophy Difference: React prefers to update everything step by step, so you always know what’s happening and can easily find and fix bugs. If React used Signals, it would have to completely change this way of working.

Practical Comparisons

Signal vs ref vs useState: A Technical Comparison

Imagine a simple counter. You want the UI to update whenever the number changes. Angular, Vue, and React each handle this differently.

Angular signals track reads and updates explicitly. When you call count(), it knows that you're using it. When you call count.set(), it updates the value and notifies anything that depends on it. It doesn't mutate the original data—instead, it replaces it. This makes state updates clear and predictable. Signals are also lazy—they only compute when needed, which helps keep things efficient, especially in larger apps.

const count = signal(0);
// Button click
count.set(5); // New reference
count.update(c => c + 1); // New reference, value: 6

Vue's ref and reactive are like an eager assistant, updating immediately when a number (ref.value) or detail (reactive property) changes. This eager approach is fast and responsive. Vue mutates values directly, which is simple and intuitive:

const count = ref(0);
// Button click
count.value = 5; // Edit value, notify

const user = reactive({ name: 'Alice' });
user.name = 'Bob'; // Edit property, notify

React's useState follows a component-centric approach, triggering re-renders and comparing the new Virtual DOM to the previous one. This model is straightforward and predictable. It's immutable by nature, creating new state objects:

const [count, setCount] = useState(0);
// Button click  
setCount(5); // Trigger re-render

Angular and Vue update only what's needed.

Angular's design is immutable and lazy, which works well for large, complex apps.

Vue updates is mutable and more eager, making it feel fast and flexible when building features quickly. (But computed properties are lazy)

React's simplicity works well for many scenarios, though complex apps may need additional optimization.

Understanding Derived Values: computed, Vue computed, and useMemo

Now, imagine you need to show the total price of books on a shelf. This is where derived values help.

Angular's computed() recalculates only when its source Signals change, and only if it's actually read. It's lazy, cached, and tracks dependencies automatically—so you don't need to think about when or how it updates. This makes it reliable, especially for things like totals, filters, or any value that depends on other state.

const price = signal(10);
const tax = signal(0.1);
const total = computed(() => price() * (1 + tax()));
// Read: 11
tax.set(0.2); // Next read: 12

Vue's computed is similarly lazy and cached. By default, computed properties are read-only, but Vue also supports writable computed when you need bidirectional logic:

const price = ref(10); 
const tax = ref(0.1);
const total = computed(() => price.value * (1 + tax.value));
// Read: 11
tax.value = 0.2; // Next read: 12

React's useMemo recalculates the value whenever the component re-renders, based on a manual list of dependencies you provide. If you forget to include a dependency, the memoized value might become outdated. It returns an immutable value, but managing dependencies manually can require extra attention:

const [price, setPrice] = useState(10);
const [tax, setTax] = useState(0.1);
const total = useMemo(() => price * (1 + tax), [price, tax]);
// Every render checks, computes: 11
setTax(0.2); // Next render: 12

Angular and Vue's lazy computed properties automatically track their dependencies. In contrast, React's useMemo requires manual dependency management, trading some convenience for explicit control.

A Thoughtful Step Forward

Angular Signals are not just another tool—they represent a thoughtful evolution in how we manage state and updates in complex applications. Their precision and predictability bring clarity to a once tangled web of change detection. While Vue and React each bring their unique strengths, Angular's approach strikes a balance that feels both deliberate and empowering.

Each framework's reactivity model serves different needs: Angular's precision for enterprise scale, Vue's intuitive developer experience, and React's predictable simplicity. Understanding these fundamentals opens the door to building frontend apps that are not only efficient but also more enjoyable to develop.

The journey with Signals is just beginning, and it's exciting to see how this evolution shapes the future of frontend development. Whether you choose Angular's functional precision, Vue's intuitive reactivity, or React's component-centric approach, the key is understanding how each model serves your application's unique needs.

1
Subscribe to my newsletter

Read articles from Claire Cheng directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Claire Cheng
Claire Cheng