What Is New in Angular 20
| Category | Highlights |
|---|---|
| New Features | Template literals, exponentiation (**) operator, in and void operators in templates; async redirectTo support; Router.getCurrentNavigation()?.abort(); NgComponentOutlet redesigned API; dynamic component bindings with inputBinding / outputBinding / twoWayBinding; keepalive support in HttpClient; experimental resource() and httpResource() APIs; Injector.destroy() on user-created injectors; ScrollOptions in ViewportScroller |
| Improvements | Signals API (effect, linkedSignal, toSignal, toObservable) promoted to stable; incremental hydration promoted to stable; route-level render mode config promoted to stable; zoneless change detection promoted to developer preview (stable in 20.2); TypeScript 5.8 and 5.9 support; type checking for host bindings; Angular DevTools Chrome Performance panel integration; template HMR enabled by default; extended diagnostic for invalid nullish coalescing; updated style guide with simplified file naming |
| Bug Fixes | ng-reflect-* attributes removed from dev mode by default (reduces DOM noise); invalid nullish coalescing operator mixing now produces compiler diagnostics; fixture.autoDetectChanges(false) throws in zoneless test mode; Injector teardown lifecycle improvements |
| Breaking Changes | Node.js 18 no longer supported (minimum: 20.11.1 or 22.11.0); TypeScript below 5.8 no longer supported; TestBed.get() removed (deprecated since v9); InjectFlags enum removed; ng-reflect-* attributes no longer emitted in dev mode by default; View Engine metadata support fully removed |
| Deprecations | HammerJS integration deprecated (removal planned in v21); ngIf, ngFor, ngSwitch structural directives deprecated (removal planned in v22); @angular/platform-server/testing deprecated (use E2E testing instead); platformBrowserDynamic deprecated in favor of platformBrowser; fixture.autoDetectChanges(boolean) parameter deprecated |
What APIs Were Stabilized in Angular 20?
Angular 20 graduates a significant set of APIs from developer preview to stable, making them safe to adopt in production code. This is the central theme of the release -- the team spent focused effort hardening features introduced in Angular 18 and 19, rather than landing a large wave of net-new APIs.
The following Signal-related APIs are now stable:
- effect() -- triggers side effects automatically when dependent signals change; supports cleanup to prevent memory leaks
- linkedSignal() -- creates a signal derived from another with optional write-back; useful for keeping form inputs in sync with data models
- toSignal() / toObservable() -- bridges between RxJS Observables and Signals, enabling incremental migration without rewriting existing reactive code
Also promoted to stable:
- Incremental hydration -- lets SSR apps hydrate only the visible or triggered parts of the page, consistently delivering 40-50% LCP improvements as measured by the Angular team
- Route-level render mode config -- granular per-route control over whether a route is server-side rendered, prerendered, or client-side rendered; no longer experimental
In practice, if your team was holding off on adopting Signals because of the developer preview status, that barrier is now gone for the core primitives. The experimental APIs to be aware of for the next cycle are resource() and httpResource(), covered below.
How Does Zoneless Change Detection Work in Angular 20?
Zoneless change detection enters developer preview in Angular 20.0 and graduates to stable in Angular 20.2, completing a multi-version effort to remove the runtime dependency on Zone.js. Instead of Zone.js intercepting every async operation globally, Angular now triggers re-renders only when signals change.
Enabling zoneless in a standalone app is a one-line change:
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()]
});
For NgModule-based apps, add provideZonelessChangeDetection() to the providers array of your root NgModule. The Angular CDK and Angular Material libraries also ship zoneless support as of this release.
The practical benefits are meaningful:
- Smaller bundles -- Zone.js is no longer included
- Cleaner stack traces -- no more intercepted promise/timer wrappers in the call stack
- More predictable rendering -- change detection runs only where signals declare a dependency
- Better interoperability with web components and micro-frontend architectures that previously had conflicts with Zone.js's global patching
Watch out for components that mutate state outside signals and expect the view to update -- those will stop working silently in zoneless mode. Migrating gradually, component by component, with OnPush as an intermediate step is the lowest-risk path for large existing codebases.
What Are the New Template Syntax Features in Angular 20?
Angular 20 significantly expands the expression language available inside templates, continuing the long-term goal of making Angular template syntax behave identically to TypeScript expressions. Four operators are newly supported.
Template literals replace verbose string concatenation:
<!-- Before -->
<img [src]="'https://cdn.example.com/users/' + userId() + '/avatar'">
<!-- After -->
<img [ngSrc]="`https://cdn.example.com/users/${userId()}/avatar`">
Note: template literals do not work when the component template itself is defined inside a TypeScript template literal string (inline template: `...`). Use a separate .html file in that case.
Exponentiation operator (**) eliminates the need for a custom pipe just to compute powers:
<!-- Renders 8 -->
<p>{{ 2 ** 3 }}</p>
The in keyword enables type-narrowing inside templates, which is particularly useful when working with discriminated union types:
@for (item of items; track item) {
@let isSpecial = 'specialProp' in item;
@if (isSpecial) {
<span>{{ item.specialProp }}</span>
}
}
The void operator solves a subtle bug where an event listener returning false would inadvertently call event.preventDefault():
<button (mousedown)="void handleClick()">Click</button>
Most teams will find template literals and the in keyword immediately useful. The void operator is niche but important if you have directives that return booleans from event handlers.
What Are the resource() and httpResource() APIs in Angular 20?
Angular 20 ships two experimental APIs -- resource() and httpResource() -- that provide a signal-native pattern for managing asynchronous data. Both APIs automatically re-execute when a dependent signal changes, exposing loading and error states as signals themselves.
resource() is the general-purpose primitive for any async operation:
import { resource, signal } from '@angular/core';
export class UserProfileComponent {
userId = signal(1);
userResource = resource({
request: () => ({ id: this.userId() }),
loader: ({ request }) =>
fetch(`/api/users/${request.id}`).then(r => r.json())
});
}
The template then consumes userResource.value(), userResource.isLoading(), and userResource.error() directly, without subscribe/unsubscribe boilerplate.
httpResource() is a convenience wrapper built on top of HttpClient:
import { httpResource } from '@angular/core/http';
import { signal } from '@angular/core';
export class UserProfileComponent {
userId = signal(1);
userResource = httpResource<User>(
() => `/api/users/${this.userId()}`
);
}
This matters if you want reactive HTTP calls that automatically cancel and re-fire when inputs change -- the pattern that most teams build manually today with switchMap and takeUntilDestroyed. Both APIs are experimental in Angular 20.x and were promoted to stable in Angular 22, so treat them as production-capable but subject to API shape changes in the short term.
What Breaking Changes and Deprecations Should You Plan for When Upgrading to Angular 20?
Angular 20 has a meaningful set of breaking changes that require attention before running ng update, particularly for teams on older Node.js or with tests that rely on Angular's debugging internals.
Environment requirements:
- Node.js 18 is no longer supported. Minimum is
^20.11.1 || >=22.11.0. Update CI/CD pipelines and deployment environments before upgrading. - TypeScript below 5.8 is no longer supported. TypeScript 5.9 support was added in Angular 20.2.
Removed APIs:
TestBed.get()is fully removed (deprecated since v9). Replace all usages withTestBed.inject().InjectFlagsenum is removed. Use object literal equivalents ({ optional: true },{ skipSelf: true }, etc.) ininject()calls.- View Engine metadata support is fully removed. Any third-party libraries that still reference View Engine will break.
ng-reflect-* attributes removed from dev mode:
Angular no longer emits ng-reflect-* DOM attributes in development mode. If your tests use selectors like [ng-reflect-value], they will start failing after upgrade. The fix is to use custom data-testid or data-cy attributes instead. If you need to temporarily restore the old behavior, add provideNgReflectAttributes() to your app providers.
Key deprecations to action now:
- HammerJS -- deprecated in v20, removed in v21. Replace gesture handling with the native Pointer Events API or an actively maintained library.
- ngIf, ngFor, ngSwitch -- deprecated in v20, removal planned for v22. Run the built-in control flow migration:
ng generate @angular/core:control-flow. The CLI will prompt you to run it duringng update. - platformBrowserDynamic -- deprecated in favor of
platformBrowser. Most standalone apps have already moved away from this. - @angular/platform-server/testing -- deprecated with no replacement. Switch SSR tests to E2E tests using Playwright or Cypress.
- File naming convention -- the style guide now recommends dropping suffixes like
.componentand.servicefrom file names (e.g.,user.tsinstead ofuser.component.ts). This is a convention change, not an enforced breaking change, but new CLI-generated files will follow the updated convention.
# Standard upgrade command
ng update @angular/cli @angular/core
# Run control-flow migration if prompted or manually
ng generate @angular/core:control-flow
Frequently Asked Questions about Angular 20
Does upgrading to Angular 20 require me to adopt Signals or zoneless change detection?
No. Signals and zoneless are opt-in. Existing Zone.js-based components using traditional reactive patterns continue to work unchanged. You can adopt both features incrementally on a component-by-component basis without changing anything else in your application.
How do I enable zoneless change detection in an Angular 20 application?
For standalone apps, pass provideZonelessChangeDetection() in the providers array of bootstrapApplication. For NgModule-based apps, add it to the providers array of your root NgModule. As of Angular 20.2, this feature is stable and no longer experimental.
My tests are failing after upgrading to Angular 20 because ng-reflect-* attributes are missing. How do I fix this?
Angular 20 no longer emits ng-reflect-* attributes in dev mode. Replace test selectors that rely on those attributes with data-testid or data-cy attributes added to your component templates. As a temporary workaround, you can add provideNgReflectAttributes() to your test module providers to restore the previous behavior while you migrate.
Are the resource() and httpResource() APIs safe to use in production with Angular 20?
Both APIs are marked experimental in Angular 20, meaning the API surface may change in a future minor release. They are functional and tested, but teams that prefer API stability should wait for them to reach stable status, which happened in Angular 22. For greenfield projects willing to accept possible API changes, they are a significant improvement over manual switchMap-based data fetching patterns.
What is the fastest path to migrate ngIf, ngFor, and ngSwitch away from their deprecated structural directive syntax?
Run the built-in migration schematic with ng generate @angular/core:control-flow from your project root. It will automatically convert ngIf to @if, ngFor to @for, and ngSwitch to @switch across your templates. The CLI also offers to run this migration automatically when you run ng update.
Do I need to update my file naming convention to match the new Angular 20 style guide?
The new style guide recommends removing suffixes like .component.ts and .service.ts in favor of simply .ts, but this is a recommendation, not a breaking change. Existing files with the old naming convention continue to work. New files generated by the Angular CLI will follow the updated convention going forward, so you may notice inconsistency in new vs. existing files within the same project.