Angular
Angular is Google’s TypeScript framework that supplies the whole front-end stack and upgrades your code between major versions with automated migrations. The stack means components, dependency injection, routing, forms, HTTP, testing and the build, chosen and released together. Between 2022 and 2026 it replaced most of the way it used to be written: standalone components instead of NgModules, signals instead of zone.js, and OnPush as the default. The page opens with every building block on one screen, the current way beside the one it replaced, then covers releases and support, dependency injection, change detection, signals and RxJS, forms and security, and five documented cases.
- Current version
- 22.2 · September 2026
- Language
- TypeScript 6.0+, required
- Major releases
- One a year, since v22
Angular on one screen: every building block, then and now
An Angular application is built from a small number of kinds of thing: components, the services they depend on, templates, inputs and outputs, and the machinery that decides when the screen is updated. Almost every one of them now has a current way and an older way that still works — and most of the material online teaches the older one. Here is all of it first. The table lists each job, the way Angular 22 does it (September 2026), the way it replaced, and the version in which the current way became stable. It is also the fastest way to tell which Angular a tutorial, a code base or a job candidate is describing.
| Job | The current way | What it replaced | Since |
|---|---|---|---|
| Organising code | Standalone components that list their own imports | NgModules declaring groups of components | v15 · default v19 |
| Getting a dependency | inject() in a field or a function | Constructor parameters — still supported | v14 |
| Component state | signal() | Plain fields, re-checked by zone.js after every event | v17 |
| Derived state | computed() | Getters, or values recalculated in ngOnChanges | v17 |
| State that follows an input but can be edited | linkedSignal() | Copying an input into a field in ngOnChanges | v20 |
| Reacting to state outside the template | effect() — sparingly | ngOnChanges, or a subscribe in ngOnInit | v20 |
| Inputs | input(), input.required() | @Input() decorators | v19 |
| Outputs | output() | @Output() with an EventEmitter | v19 |
| Two-way binding | model() | An @Input() plus an @Output() whose name ends in Change | v19 |
| Reaching child elements | viewChild(), contentChild() | @ViewChild, @ContentChild | v19 |
| Conditions and loops in templates | @if, @for (…; track …), @switch | *ngIf, *ngFor, *ngSwitch — deprecated in v20 | v18 |
| A local variable in a template | @let | The *ngIf="data as d" trick | v19 |
| Loading part of a page later | @defer blocks | A lazy route, or a hand-written dynamic import | v18 |
| Loading data | resource(), httpResource() | HttpClient plus subscribe plus loading and error flags kept by hand | v22 |
| Forms | Signal Forms, form() | Reactive Forms and template-driven forms — neither deprecated | v22 |
| What starts a check | Zoneless: a signal change, a template event, markForCheck | zone.js patching every async browser API | v20.2 · default v21 |
| What gets checked | OnPush, by default | The Default strategy, now named Eager | default v22 |
| HTTP transport | fetch | XMLHttpRequest — still there as withXhr() for upload progress | default v22 |
| Server rendering | Incremental hydration: @defer (hydrate on …) | Hydrating the whole page at once — or, in older versions, rebuilding it | v20 · default v22 |
| Unit tests | Vitest | Karma and Jasmine | default v21 |
| Ending a subscription | takeUntilDestroyed(), or the async pipe | A Subject completed in ngOnDestroy | v19 |
Everything decided, and upgraded for you
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 them, chosen, integrated and released together. Whether that sounds like relief or like a cage is the honest test of whether the framework suits you. The consequence that matters over years rather than weeks is uniformity: two Angular code bases written by different teams look substantially alike, so an engineer moving between them is productive quickly.
The other consequence is unusual for a framework: Angular takes responsibility for moving your code forward. ng update runs migrations — scripts that rewrite your source for the new version. When v21 stopped using zone.js by default, the migration added provideZoneChangeDetection() to existing applications so they kept working as before. When v22 made every component without an explicit strategy OnPush, the migration added ChangeDetectionStrategy.Eager to exactly those components, so an upgrade did not silently change how any of them behaved. The new default applies to new code; the old behaviour is kept, written down, where it was already relied on.
Migrations only help inside the support window, and the window is short. Each major version is “active” for a while — it gets fixes and small features — and then enters LTSLong-Term Support — a release patched for yearsA release line that keeps receiving security and critical fixes for a published period after newer versions ship, but no new features. It lets a team upgrade on its own schedule — until the end date on the support table, after which the next vulnerability has no fix at all.All terms, when only critical fixes and security patches are released. Everything older gets nothing. Until v22 a major version came every six months; from v22 it comes once a year, with four to six minor releases in between, which makes each window longer and each upgrade larger.
| Version | Released | Active until | Security fixes until |
|---|---|---|---|
| v22 | 3 Jun 2026 | Jun 2027 | Jun 2028 |
| v21 | 19 Nov 2025 | 3 Jun 2026 | Jun 2027 |
| v20 | 28 May 2025 | 19 Nov 2025 | 28 Nov 2026 |
| v2 – v19 | 2016 – 2024 | — | None |
Angular
- Router, forms, HTTPHyperText Transfer Protocol — the protocol of the webThe protocol browsers and servers talk in: the client sends a method and an address, the server answers with a status code and a body. It keeps no memory between requests — every request stands alone, which is why anything that must be remembered is carried explicitly.All terms, testing supplied and integrated
- TypeScriptJavaScript with type checkingJavaScript plus a description of what each function takes and returns, checked in the editor before anything runs. It never reaches the browser — it is compiled back into plain JavaScript first, so the types are a development-time tool and nothing more.Full article →All terms and dependency injection are not optional
- Automated migrations across major versions
- A large concept load up front, uniform afterwards
- Templates compiled ahead of time
React
- A component model; the rest is your choice or a framework’s
- TypeScript optional, dependency injection unusual
- Upgrades coordinated across independent packages
- A small core; complexity accumulates in the choices
- JSXmarkup written inside codeA syntax that lets you write markup directly in JavaScript. It is not HTML embedded in code but a shorthand: before the code runs, every tag is turned into an ordinary function call.Full article →All terms — the view is ordinary JavaScript
The choice is rarely technical. Angular suits an organisation with many teams, long-lived products and turnover, where one answer per question is worth more than the best answer per question. React suits a team that wants to assemble its own stack and can carry the cost of maintaining that assembly. Both build the same applications; what differs is who makes the decisions and who lives with them.
Dependency injection: where you provide it decides how many exist
A component does not construct the things it needs. It asks for them — private http = inject(HttpClient) — and the framework hands it an instance, without the component knowing where the instance came from or who else is using it. The object that keeps instances and hands them out is called an injector, and Angular has a tree of them.
The payoff is substitution. A test replaces the real HTTP client with a fake by changing one provider, not by patching an import. A component that logs through an injected logger can be given a different logger in a different environment. The cost is indirection: reading a component tells you what it depends on, but not which implementation will arrive. That trade is worth it when the substitution is used, and pure overhead when it is not — a service injected in one place and never replaced has bought nothing and made the code one hop harder to read.
When a component asks for something, Angular looks first in the component’s own injector, then in its parent’s, up the tree of components, and then in the application’s root injector. The first one that has a provider wins. If none has, the request fails with NG0201: No provider for … found — the error every Angular developer meets in the first week, usually because a service was never registered anywhere. Where you register a service therefore decides how many instances exist and how long each one lives:
| Provided in | How many | Lives until | Right for |
|---|---|---|---|
providedIn: "root" | One for the whole application | The tab is closed | Stateless services, the API client, the signed-in user, caches |
A component’s providers | One per instance of that component | That instance is destroyed | The state of one widget, one form, one step-by-step wizard |
How the framework finds work to do
Angular has to decide when to compare each component’s template with its data and update the screen. That comparison is called change detection. For most of Angular’s life the answer to “when” came from zone.js, a library that patches every asynchronous browser APIApplication Programming Interface — contract between programsThe rules one program follows to call another: which requests it may send, in what shape, and what comes back. To the caller it is a contract — everything behind it stays a black box, which is exactly what lets the other side be rewritten without breaking you.Full article →All terms — timers, event listeners, network requests — so that Angular is told whenever anything at all finishes. On that signal, Angular walked the component tree and re-evaluated the template expressions.
That is less wasteful than it sounds. Angular compiles templates ahead of time, so checking an expression is a property comparison, not an interpretation, and thousands of them are fast. It turns into a problem at scale and in particular shapes: a large tree, a template calling a function that runs on every check, or an event that fires continuously, like scrolling or a mouse moving over a chart.
The first answer was OnPush, and the diagram shows what it buys. A component marked OnPush is checked only when one of its inputs receives a new reference, when an event bound in its own template fires, when a signal its template reads changes, or when something calls markForCheck — which the async pipe does for you. Everything else is skipped, along with its subtree. The cost stops depending on the size of the application and starts depending on where the change is — provided inputs are replaced rather than changed in place, which is why OnPush and immutable updates always arrive together. Since v22, a new component is OnPush unless it says otherwise; the old behaviour, which checks everything, is now called Eager.
Zoneless: knowing instead of guessing
zone.js answers the question “did anything finish?”, and the honest answer is almost always yes: a timer in an analytics script, a mouse move handled by a chart library, a polling request that changed nothing. Each one started a check. Signals let Angular ask a better question — “did anything the templates read change?” — because a signal knows who read it. Zoneless Angular, stable since v20.2 and the default for new applications since v21, drops zone.js altogether and starts a check only for a signal change, an event bound in a template, or an explicit markForCheck.
The practical effect is less JavaScript shipped, no patched browser APIs, and strictly less work per interaction. The one thing that breaks is code that relied on zone.js noticing a change nobody announced — a plain field assigned inside a setTimeout in an OnPush or zoneless component. The fix is the same in every case: hold that value in a signal.
Signals for state, RxJS for events over time
A signal is a value that records who read it, much like a ref in Vue. signal() holds a value you set; computed() derives one from other signals, lazily and only when they change; linkedSignal() follows a source but can also be set by hand — a selected item that resets when the list changes. Inputs are signals too: name = input<string>() is read as this.name(), so anything derived from inputs is a computed, and most of what ngOnChanges used to do disappears.
effect() is the one to be careful with, and Angular’s guidance matches React’s on useEffect: it is for synchronising with something outside the signal graph — local storage, a chart library, a log — not for keeping one piece of state in step with another. An effect that sets a signal is a computed written the long way, with a moment in between when the two disagree.
Loading data got its own signal API, stable since v22. httpResource(() => "/api/orders/" + this.id()) returns an object whose value, status and error are signals, with six statuses — idle, loading, reloading, resolved, error and local, the last meaning the value was set by hand. When the id changes while a request is still in flight, the resource aborts it. That removes, by construction, the race where a slow old answer overwrites a fast new one — the race the React and Vue pages each solve by hand.
Where RxJS still earns its place
RxJS models a value that arrives many times — a stream — and gives you operators to transform, combine, delay and cancel streams. Angular’s router events and form value changes are streams, so you will meet it whether or not you go looking. Where it earns its complexity is coordination over time: a search box that waits for typing to pause, cancels the previous request on each new keystroke, ignores answers that arrive out of order and retries on failure is four operators and about six lines. Written by hand it is thirty lines with a bug in the cancellation.
Most of that power lives in one decision: what to do with a request that is already running when a new value arrives. RxJS has four operators for it, and choosing the wrong one produces a bug that looks nothing like its cause.
| Operator | The running request | Use it for | In the wrong place |
|---|---|---|---|
| switchMap | Cancelled; only the latest counts | Search as you type, a route parameter | On saves: a cancelled save is a lost write |
| mergeMap | Kept; all run in parallel, answers in any order | Independent uploads, logging | On search: an old answer can land last |
| concatMap | Kept; the new one waits its turn | Saves that must reach the server in order | On search: a queue of answers nobody wants |
| exhaustMap | Kept; the new value is ignored | A pay or submit button pressed twice | On search: typing is ignored while a request runs |
Where RxJS hurts is a value that exists 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 has to subscribe, unsubscribe and handle the initial state. Signals fit that shape better, and the two are meant to live side by side: signals for state, RxJS for events over time, with toSignal() and toObservable() to convert between them.
Forms, and the safety you get by default
Forms are Angular’s strongest ground, and since v22 there are three ways to build them. None is deprecated, and they differ in where the form’s model lives:
| Approach | Where the model lives | Right for |
|---|---|---|
| Template-driven | In the template, bound with ngModel | A login form, a search box |
| Reactive Forms | In the class, as FormGroup and FormControl objects, typed since v14 | Existing applications, forms built at run time |
| Signal Forms | In a signal holding the data, with validation rules in a schema | New code on v22 — experimental in v21, public API since v22 |
Security is the part you get without asking, and it is worth knowing exactly what it covers. Angular treats every value bound in a template as untrusted. Text in {{ }} is escaped, so a comment containing <script> shows up as text. A value bound to [innerHTML] is sanitised — scripts and dangerous attributes are stripped — and URLs in href and src are checked. Templates are compiled when the application is built, so in production there is no template compiler running in the browser that user input could reach. And HttpClient sends the anti-forgery token — read from the XSRF-TOKEN , sent as the X-XSRF-TOKEN header — on requests that change data, only to your own origin, which leaves the server one thing to do: check it.
Five cases, and what each one was missing
Each case below is documented in Angular’s own changelogthe record of what changed, per versionA file, usually CHANGELOG.md, that lists the notable changes in each version of a piece of software, newest first, grouped as added, changed, deprecated, removed, fixed and security. It is written for the person deciding whether to upgrade, which is why a dump of commit messages does not count as one.Full article →All terms, guides or security advisories. Together they explain the shape of the framework today — why it invests so heavily in migrations, why templates are compiled before they ship, and why the version you run matters as much as the code you write.
| Case | What happened | What was missing |
|---|---|---|
| AngularJS to Angular, 2014–2021 | Angular 2 was a new framework with the old name, announced without an upgrade path | A migration path — the reason ng update exists |
| The expression sandbox, 2016 | AngularJS removed a sandbox that teams had mistaken for a security boundary | The rule that users never write templates — compiled templates enforce it now |
| NgModules, 2016–2024 | Added five weeks before 2.0, they were the hardest concept for newcomers for eight years | A compiler that could read each component on its own |
| View Engine to Ivy, 2020–2023 | The rendering engine was replaced; libraries built for the old one stopped working in v16 | Dependencies that moved as fast as the framework |
| CVE-2025-66035, Nov 2025 | HttpClient sent the anti-forgery token to other hosts; the fix shipped only for supported versions | Being inside the support window |
The first case is the reason the second section of this page exists. AngularJS, released in 2010, was one of the most popular front-end frameworks in the world when Google announced its successor at the ng-europe conference in Paris in October 2014. Angular 2 kept the name and very little else — a new component model, a new template syntax and, from 2015, TypeScript — and at the announcement it had no upgrade path at all. The reaction was fierce, and the team spent the next two years building one: ngUpgrade, which lets both frameworks run in one application while it is moved over piece by piece. Angular 2.0 shipped on 14 September 2016. AngularJS entered long-term support in July 2018, had it extended by six months during the pandemic, and lost it on 31 December 2021; its final release, 1.8.3, is called “ultimate-farewell”. Every automated migration Angular has shipped since is, in part, an answer to 2014.
The third and fourth cases are the same story told twice. NgModules arrived in August 2016, in the fifth release candidatea build meant to become the releaseA build the team believes is ready to ship, published so users can try it before the final release. Bugs found now are fixed and no new features are added, but details can still change — so it is for testing, not for production.All terms, five weeks before 2.0 — mostly because the compiler needed a context to know which components a template was allowed to use. For eight years they were the concept newcomers found hardest, and the part of every tutorial most likely to be wrong. What removed them was a new rendering engine. Ivy, the default from v9 in February 2020, compiles each component with everything it needs to know about itself, so a component can list its own imports. Standalone components became stable in v15 and the default in v19, in November 2024. The engine swap had a cost of its own: libraries compiled for the old engine, View Engine, ran through a compatibility compiler until v16 removed it in May 2023, with the changelog’s plain warning — “Angular View Engine libraries will no longer work.” An application can move no faster than its slowest dependency, and some teams found theirs had stopped being maintained.
Where it degrades
Angular’s reputation for being heavy is mostly about the first two weeks. The concept load really is front-loaded — dependency injection, decorators, signals, RxJS, change detection — and a developer who has only used React will be slower for a fortnight. What that buys is a fifth year that looks like the first. When an Angular code base does degrade, it is rarely the framework’s machinery that fails; it is the older column of the table living on, or a feature used against its grain.
- Subscriptions that are never ended, which keep destroyed components and their DOM alive.
- Function calls in templates, which run on every check rather than when the data changes.
OnPushwith inputs changed in place, so the component never updates and looks broken at random.- Effects that set signals, which is a
computedwith a moment of disagreement built in. - Nested subscriptions instead of
switchMap, which bring back the race the operator exists to remove. - Per-screen state in a root service, which survives navigation and leaks between visits.
bypassSecurityTrust…on content that came from users.- New code written in NgModules and
*ngIf, which is the subject of the next migration. - A version outside the support window, where the next security advisory has no patch.
The honest limit is the framework’s own premise. Angular decides for you, and that is only an advantage while its decisions fit your problem. A mostly static content site pays for a full framework runtime it does not need; a small team that wants to try a new state library or router every year will fight the framework rather than use it. And uniformity cuts both ways: every Angular application inherits the same migration schedule, whether or not the team has time for it that quarter.
The practical check is two commands. ng version says which row of the release table you are on, and whether you are inside the window. ng update lists what is waiting and runs the migrations — and doing it at every major, rather than every third, is the difference between an afternoon and a project.
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 migrations across major versions are worth real money.
- Complex forms and coordinated asynchronous flows — the framework’s strongest ground.
- The team wants TypeScript and dependency injection as a given rather than a choice.
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 — it will fight the framework rather than use it.
- A mostly static content site, where a full framework runtime buys nothing.
- No one will own upgrades — a version left behind loses its security fixes within two years.
Sources and method
What this page stands on
- 9 sources
- 1 documented failure
- Checked October 13, 2026
Practices and where the terms come from
every building block, the version it became stable in, and the support table- DocumentationVersioning and releases (opens in a new tab)angular.dev
- DocumentationRoadmap (opens in a new tab)angular.dev
- DocumentationSignals — Overview (opens in a new tab)angular.dev
- DocumentationhttpResource (opens in a new tab)angular.dev
- Documentationangular/angular — CHANGELOG (opens in a new tab)GitHub
AngularJS and the rewrite
the AngularJS cases in “Five cases, and what each one was missing”The failures this page learns from
CVE-2025-66035, the last of the five casesResearched with Claude. Finding and cross-checking the primary sources was done with Claude (Anthropic). Selection, writing and fact-checking: the Anatomy of IT Product editors.
Found this useful?
Share it with someone who is working on the same problem.