Skip to content
QSWEQB
hardConceptDesignMidSeniorStaff

Angular, Vue and React are answers to the same problems. Compare how each handles change detection, reactivity and dependency injection.

Each must decide what to re-render when data changes, how derived values stay correct, and how a component gets a dependency it did not create. Angular answers with dirty checking now moving to signals plus a hierarchical injector, Vue with proxy-based per-property tracking, React with convention plus memoisation.

5 min readUpdated 2026-07-26

What the interviewer is scoring

  • Does the candidate compare invalidation models rather than syntax and ecosystem size
  • Whether they can state what each framework treats as the unit of re-render
  • That they know Angular signals track reads and therefore narrow change detection rather than replacing RxJS
  • Whether they describe Vue reactivity as per-property proxy tracking and not as watchers on components
  • Does the candidate separate real dependency injection from a context or provide lookup

Answer

Three problems every one of them has to solve

Strip the branding away and each framework owes you answers to the same three questions. When a piece of data changes, how does the framework find out, and how much of the UI does it then reconsider? When one value is derived from another, what keeps the derived value correct without recomputing the world? And when a component needs a service it did not construct, how does it get one, and how do you substitute a different one in a test?

Reading the three side by side is the fastest way to understand any one of them, because the differences are not stylistic. They are three different bets about where to put the bookkeeping.

What each treats as the unit of invalidation

Angular's classical model is dirty checking. zone.js monkey-patches the asynchronous browser APIs — timers, event listeners, XMLHttpRequest — so that when any of them fires, Angular knows something might have changed and runs a change-detection pass. That pass walks the component tree from the root, evaluates each template binding, and compares the result with the previous value. Nothing tracked which data changed, so the default strategy is to check everything and see. ChangeDetectionStrategy.OnPush is the escape hatch: a component with it is skipped unless an input reference changed, an event fired from its own template, or something called markForCheck().

Vue never had to guess, because it tracks reads. Reactive state in Vue 3 is a Proxy, and every property get during a render registers a dependency between that exact property and the component's render effect. When the property is set, only the effects that read it are invalidated. The practical consequence is that a Vue component behaves like an OnPush Angular component without anyone opting in, and the granularity is per property rather than per object. Vue 2 built the same idea on Object.defineProperty getters and setters, which is why adding a property to an object or assigning to an array index was invisible to it and needed Vue.set; the move to Proxy in Vue 3 removed that whole class of surprise.

React makes the opposite trade deliberately. There is no tracking at all: setState marks the calling component dirty, React re-renders it, and re-rendering a component produces new child elements, so by default its subtree re-renders too. Correctness comes free — nothing can be stale, because everything downstream ran again — and the cost is that you pay for renders you did not need. That is what memo, useMemo and useCallback exist to claw back, and it is why React Compiler, which reached a stable release, is significant: it inserts that memoisation at build time from the code you already wrote rather than asking you to hand-annotate it.

Signals put Angular on Vue's side of the line

Signals arrived in Angular 16 and the core primitives were marked stable in v17, with signal-based input(), model() and view queries following through the 17.x releases. A signal is a value with a read function; reading it inside a template or a computed registers the dependency, and writing it notifies the dependents. A computed is lazy and memoised, so it recalculates on the next read after a dependency changed and not before, and Angular resolves the graph so that no consumer ever observes a half-updated set of values.

// Angular: the template read registers a dependency, so a write to `count`
// marks only the components that read it, not the whole tree.
count = signal(0);
double = computed(() => this.count() * 2);
// Vue: the same graph, expressed as a ref plus a cached computed.
const count = ref(0)
const double = computed(() => count.value * 2)
// React: nothing tracked the dependency, so `double` is simply recomputed on
// every render of this component - which is why the render itself must be cheap.
const [count, setCount] = useState(0);
const double = count * 2;

This is what makes the zoneless work possible: once reads are tracked, Angular no longer needs zone.js to tell it that something asynchronous happened, because the write itself is the notification. Zoneless change detection landed as an experimental provider in v18 and has been stabilising since. Note what signals are not. They are not a replacement for RxJS, and saying so is a common way to lose credit — signals model synchronous current-value state, whereas RxJS models streams of events over time with operators for cancellation, retry and combination. An application that pushes HTTP responses and WebSocket streams through RxJS and holds the resulting component state in signals is using both correctly.

Dependency injection versus looking something up

Angular is the only one of the three with genuine dependency injection. There is a hierarchy of injectors — a root injector, injectors created by lazily loaded routes, and element injectors on component nodes — and a lookup walks up that hierarchy from the point of injection. Dependencies are requested by token, provided in a way that can be swapped (useClass, useValue, useFactory), and resolved through inject() or a constructor parameter. That indirection is the reason Angular tests can replace an HTTP client with a fake without the component under test knowing.

Vue's provide/inject and React's context solve a narrower problem: getting a value to a deep descendant without threading it through every intermediate component. There is no lifetime management, no scoping beyond the component tree, and no token registry — the key is a string or symbol, and in Vue an InjectionKey<T> exists purely to make the retrieved value typed. Their behaviour also differs in a way that matters at scale: a React context whose value changes re-renders every consumer, so people split contexts to limit the blast radius, while a Vue component injecting a ref re-renders only if it actually reads the property that changed.

Comparing APIs when you were asked about models

The answer that gets marked down is a feature list — templates versus JSX, opinionated versus unopinionated, CLI quality, ecosystem size. All of that is true and none of it explains why the same application performs differently, so it reads as familiarity with three toolchains rather than understanding of one problem. The version that earns senior marks names the invalidation unit for each framework and then draws the consequence: because React re-renders subtrees, keeping render functions pure and cheap is a load-bearing rule rather than style advice; because Vue and Angular signals track reads, an accidental read of a coarse object in a hot template is how you lose the granularity you were relying on.

One factual trap sits inside this. Do not describe Angular as having two-way binding with a digest cycle — that was AngularJS, a different framework. In Angular, [(ngModel)] and in Vue v-model are compile-time sugar for a property binding plus an event handler, so the data still flows one way in each direction and there is no loop that re-runs until values settle.

Likely follow-ups

  • Vue re-renders a component only when state it read changes. Why does React not simply do the same thing?
  • What did zone.js buy Angular, and what does going zoneless cost the application code?
  • You have a value that three unrelated subtrees need. How does the answer differ between Angular DI, Vue provide and React context?
  • Where does RxJS still belong in an Angular application once component state has moved to signals?

Related questions

Further reading

angularvuereactchange-detectionreactivitydependency-injection