Angular
Angular is a framework rather than a library, and the difference is the whole point: it supplies dependency injection, routing, forms, HTTP, testing and a build system, and expects you to use them. That removes a hundred small decisions and replaces them with one large one. This page covers dependency injection, change detection and what signals changed, and where the framework earns its weight.
- Kind
- Full framework
- Language
- TypeScript, required
- Current model
- Standalone · signals
Everything decided, on purpose
Starting a React project means choosing a router, a data layer, a form library, a styling approach and a build setup before writing a feature. Starting an Angular project means running one command and getting all of those, chosen, integrated and versioned together. Whether that sounds like relief or like a cage is the honest test of whether this framework suits you.
The consequence that matters over years rather than weeks is uniformity. Two Angular codebases written by different teams look substantially alike, because the framework made the same decisions in both. An engineer moving between them is productive quickly, and an upgrade guide from the Angular team applies to your project rather than to a hypothetical one — the CLI ships automated migrations that rewrite your code for each major version, which is a genuinely unusual thing for a framework to take responsibility for.
Dependency injection is the spine
A component does not construct the things it needs; it declares that it needs them, and the framework supplies an instance. In practice that means a class asks for HttpClient or UserService in its constructor and receives one, without knowing where it came from or who else is using it.
The payoff is substitution. A test replaces the real HTTP client with a fake by changing one provider, not by patching a module import. A component that logs through an injected logger can be given a different one in a different environment. And because the injector is hierarchical, a service provided at the root is a singleton for the application, while one provided on a component is created fresh for each instance of that component and destroyed with it — which is how you give a widget its own isolated state without inventing a scoping mechanism.
The cost is indirection. Reading a component tells you what it depends on but not which implementation will arrive, and tracing that means understanding the injector hierarchy above it. This is the same trade every DI container makes, and it is worth it when the substitution is used and expensive when it is not — a service injected in one place and never replaced anywhere has bought you nothing and made the code one hop harder to read.
How the framework finds work to do
Angular has to decide when to re-check the template of a component against its data. Historically it answered that with zone.js, a library that patches every asynchronous browser API — timers, event listeners, XHR — so the framework is notified whenever anything at all finishes. On that notification, Angular walks the entire component tree and re-evaluates every template expression.
It is worth being precise about why that is not as wasteful as it sounds. Angular compiles templates ahead of time, so checking an expression is a property comparison rather than an interpretation, and thousands of them are fast. It becomes a problem at scale and in specific shapes — a large tree, a template calling a function on every check, or an event that fires continuously like a scroll or a mousemove.
The first answer is OnPush, and the diagram shows what it buys. A component marked OnPush is re-checked only when one of its inputs changes by reference, when an event fires inside it, or when an observable it renders with the async pipe emits. Everything else is skipped, along with its subtree. That turns the cost from "the size of the application" into "the depth of the path to the change" — but only if inputs are replaced rather than mutated, which is why OnPush and immutable updates always arrive together.
Signals — knowing instead of checking
Signals replace the question entirely. A signal is a value that records who read it, exactly like Vue's reactivity: computed() derives from other signals and recalculates only when they change, and a template that reads a signal registers itself as a consumer of that specific value. When the signal is set, Angular does not need to check anything — it knows which templates depend on it.
The practical consequence is that zone.js becomes optional. A zoneless Angular application ships less JavaScript, no longer patches the browser's async APIs, and does strictly less work per interaction. For new code the guidance is straightforward: use signals for component state, computed for anything derived from it, and treat effect the way React treats useEffect — as an escape hatch for synchronising with the world outside, not as a place to put logic.
RxJS: where it helps and where it hurts
RxJS models a value that arrives many times over — a stream — and gives you operators to transform, combine, delay and cancel those streams. Angular uses it for HTTP responses, router events and form value changes, so you will meet it whether or not you go looking.
Where it genuinely earns its complexity is coordination over time: a type-ahead search that debounces input, cancels the previous request when a new keystroke arrives, ignores out-of-order responses and retries on failure. That is four operators and about six lines, and writing it by hand is thirty lines with a bug in the cancellation. Nothing else in the front-end toolbox does that as well.
Where it hurts is when it is used for a value that has one at a time. A BehaviorSubject holding the current user is a stream with one item, wrapped in machinery designed for many — and every consumer now has to subscribe, unsubscribe and handle the initial state. Signals are the better fit for that shape, and the two coexist deliberately: signals for state, RxJS for events over time, with conversion helpers between them.
Angular against React
Angular
- Router, forms, HTTP, testing supplied and integrated
- TypeScript and DI are not optional
- Automated migrations across major versions
- Large upfront concept load, uniform afterwards
- Templates the compiler can analyse
React
- A component model; everything else is your choice
- TypeScript optional, DI unusual
- Upgrades coordinated across independent packages
- Small core, complexity accumulates in choices
- JSX — the view is ordinary JavaScript
The choice is rarely technical. Angular suits an organisation with many teams, long-lived products and turnover, where having one answer per question is worth more than having the best answer per question. React suits a team that wants to assemble its own stack and can carry the responsibility of maintaining that assembly. Both build the same applications; what differs is who makes the decisions and who lives with them.
How this shows up in real delivery
Angular's reputation for being heavy is mostly about the first two weeks. The concept load is genuinely front-loaded — DI, decorators, modules or standalone imports, RxJS, change detection — and a developer who has only used React will be slower for a fortnight. What that buys is that the fifth year looks like the first: the framework upgrades itself with migrations, the structure is the same across teams, and a developer joining an eight-year-old Angular codebase finds the conventions they already know.
The one thing to check before adopting it today is which Angular your team is actually learning. There is a lot of material describing NgModules, zone-based change detection and BehaviorSubject-driven state — an approach that works but that the framework has moved on from. Starting a project in the older style in 2026 means writing code that the next migration guide will be about.
Where it degrades
- Subscriptions without unsubscription, which keep destroyed components and their DOM alive.
- Function calls in templates, which re-run on every change detection pass rather than when the data changes.
OnPushcombined with mutated inputs, so the component never updates and looks broken at random.- RxJS used for single values, which wraps a variable in subscription machinery.
- Nested subscriptions instead of
switchMap, which reintroduces the race the operator exists to remove. - Services provided in root that hold per-screen state, which survives navigation and leaks between users of the screen.
- New code written in the NgModule and zone style, which is the subject of the next migration.
When to use it
Use it when
- A large organisation with several teams that benefits from one answer per question.
- A long-lived product where automated migrations across major versions are worth real money.
- Complex forms and coordinated asynchronous flows, which are the framework's strongest ground.
- The team wants TypeScript and dependency injection as defaults rather than as choices.
Avoid it when
- A small project or a prototype, where the concept load is paid up front and never recovered.
- A team that wants to assemble its own stack — the framework will be fought rather than used.
- A mostly static content site, where a full framework runtime buys nothing at all.
- Nobody will learn RxJS or signals properly — both failure modes here are silent.
Found this useful?
Share it with someone who is working on the same problem.