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


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 behindAngular
,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:
Creating a signal sets up a data node that holds its value and its dependents.
Reading the signal (like calling
count()
) registers an active consumer (such as an effect or computed) to that signal.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 likeVue
, 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
andlazy
, which works well for large, complex apps.Vue updates is
mutable
and moreeager
, 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
andVue's
lazycomputed
properties automatically track their dependencies. In contrast,React
'suseMemo
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.
Subscribe to my newsletter
Read articles from Claire Cheng directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
