Not a framework war. A working engineer's honest accounting.
The Setup
I spent four years at Phenom (a B2B HR tech company) working on three concurrent frontend contexts:
- A large internal product built in Angular (v9 → v15, ~500 components, 5,000+ active users)
- A Pilot product started in React, with the stated USP of eventual React Native migration
- A Chrome extension that injected an iframe and loaded a React application inside it
This isn't a theoretical comparison. I lived in both codebases every week for nearly four years. The React product never launched as an app — it shipped as a responsive website instead. The Angular product is still running.
I'm biased toward Angular. I know it. This article explains why, and I'll let you decide if the bias is earned.
What React Actually Gives You
React is not a framework. It's a UI library. That distinction sounds academic until you're six months into a team of eight and you realize no two engineers made the same architectural call.
In the Pilot React product I worked on, the early team had to make decisions that Angular would have made for them:
- State management: Redux? Zustand? Context? Jotai? Each had advocates.
- Routing: React Router v5 → v6 introduced breaking changes mid-project.
- Styling: styled-components vs. SCSS modules, tailwind? never fully resolved.
- File structure: flat, feature-based, domain-driven, three opinions, no standard.
I joined when the product had 5 components in wireframe. By the time I transitioned out, it had approximately 200 components. The architectural debt from those early unconstrained choices compounded with every component added.
React gives you freedom. Freedom without structure, at scale, becomes liability.
What Angular Actually Forces You To Do
Angular forces structure before you write a single line of business logic.
Every feature has a dedicated template, stylesheet, component class, service, and route configuration. There are interceptors for HTTP. There are guards for navigation. There's a module system, and now, standalone components which makes you think in boundaries, not just files.
When I led the Angular upgrade from v9 → v12, and later identified standalone components in v15 as an architectural improvement and made the business case to leadership - the migration was significant but tractable. ~500 components. A team of 12–15 engineers. The framework gave us enough shared language that we could execute it without rebuilding the mental model.
That would have been a different story in React. A comparable refactor migrating from one state management library to another which has no prescribed path. Every team invents its own.
Angular Signals, introduced in recent versions, close the last real gap React had in reactivity ergonomics. There is no longer a compelling reactive programming argument for React over Angular in enterprise contexts.
Dependency Injection Is Not About Testability. It's About Time.
Everyone says Angular DI makes testing easier. That's true, but it's the wrong headline.
The real payoff shows up three years later, when someone maybe you, maybe not opens a file they didn't write.
Compare these two lines doing the same thing:
Angular:
const a = someFunction();
const c = dependencyService.validateThisService(a);
console.log(c);
React:
const a = someFunction();
const c = validateThisService(a);
console.log(c);
A React developer will say: just click through to the import. You'll see where it's from.
That's fair. If you're in the file for the first time today, in a familiar codebase, with good IDE support.
But visit that line 3 years later, in a file you didn't write, in a codebase that's rotated engineers twice. Now validateThisService could be a local utility, a shared library function, a barrel export from a utils folder, or something that's been copy-pasted and diverged.
You have to trace it.
In Angular, dependencyService.validateThisService(a) tells you something before you click anything. There is a service. It owns this validation. If you want to know what else that service does, it's one tab. If you want to know where it's injected from, it's the constructor. The code has a return address.
The function call in React has no return address. It's floating. It could be from anywhere. And in a 200-component codebase with three engineers who made different structural choices — it often is.
This is not a readability debate.
This is the difference between code that documents its own dependencies and code that delegates that job to whoever's reading it at the worst possible time.
Adults write code that explains itself after they've left the building.
Internationalization Is an Afterthought Everywhere Except Angular
Angular ships with @angular/localize. One package. Built by the same team that built the framework. No third-party dependency to evaluate, no community split between competing libraries.
The translation workflow is straightforward: mark strings, extract them, hand off one file to your translators, get it back, done.
When I delivered multilingual features for Fortune 500 EU clients, the translation file was the artifact. Not a collection of JSON files spread across feature folders. Not a barrel export someone assembled. One file with a clear structure that a non-engineer translator could open and work with.
That's the adult version of internationalization. It doesn't make you feel clever. It just works, every time, the same way.
The Manager Observation
This is the part where I'll lose some readers, but it tracks with my experience.
Technical managers who have shipped and maintained large frontend codebases over 3+ years tend to reach for Angular. Not because it's trendy — because they've felt the cost of the alternative.
Managers who have moved through roles quickly, prioritizing hiring speed and ecosystem breadth, tend to reach for React. The React ecosystem is wide. That width reads as optionality. But optionality without constraint is just deferred decision-making — and someone downstream pays for it.
This is not an attack on React engineers. It's an observation about what happens when framework choice is driven by hiring market size rather than long-term maintainability.
The Chrome Extension Context
The Chrome extension was React. Injecting an iframe into third-party pages and loading a full React application inside it. This context actually suited React well: isolated scope, small surface area, no long-running state across sessions. Framework overhead didn't compound the way it does in a 500-component product.
This is the honest answer: React is not wrong. It's often wrong for the context it gets chosen for.
My Actual Position
Angular is boring. It's verbose. It has a learning curve that filters out engineers who won't invest in understanding it. It is not exciting to maintain. It is not flexible. It will not let your team build whatever they want however they want.
That is exactly why I use it for serious work.
The frontend ecosystem rewards novelty. Angular rewards discipline. For enterprise, long-running, high-user-count applications — I have not yet seen a credible argument that the tradeoffs favor React.
If you're building a startup MVP with a team of two and a 6-month runway, use whatever ships fastest. But if you're building something that will be maintained by 10+ engineers across 4+ years, the structure Angular enforces is not overhead, it's insurance.
I'm biased. But I've done the work that earns the bias.
Tags: angular react frontend architecture webdev
Top comments (0)