What's New in Angular 20
Waiting for some new exciting features? Angular 20 is definately a version improving the framework in so many aspects but it's more a version paving the way for the future or stabilizing some APIs. Some more Signals APIs are now stable and ready to be used in production: effect, linkedSignal, toSignal and toObservable. Checkout their history on Angular Can I Use.
While not being stable yet, Zoneless change detection has been promoted in preview: get ready for a brighter future and enable it on new Angular projects with a new dedicated prompt on `ng new` command.
You expected an experimental release for Signal Forms or Selectorless Components? These features are not ready yet as the Signal Forms prototype is still on a separate branch on Angular Repository but you can still check its docs to get an idea of what's coming. No public code is available yet in the Angular Repository for Selectorless Components.
Technical Requirements
- TypeScript 5.8 or later
- Node.js 20.11.1 or later
Prerequisites
Typescript
Angular 20 drops support for TypeScript versions less than 5.8 and defaults to 5.8.2 on a new/migrated project. For a full list of TypeScript 5.8 changes, visit: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html
Type checking of host bindings
Angular 20 adds a new flag to the strict config: typeCheckHostBindings
.
When enabled, the compiler will type-check the host bindings of the component:
You can enable it manually in your tsconfig.json
file and it’s planned to be defaulted in v21 Angular projects:
{ "angularCompilerOptions": { // ... "typeCheckHostBindings": true },}
Type-checking fix for host listener decorators
The wrong info was used for @HostListener
decorators, displaying the method name instead of the event name:
Argument of type 'Event' is not assignable to parameter of type 'string'.Argument of type 'MouseEvent' is not assignable to parameter of type 'string'.
Relative tsconfig paths in references
tsconfig paths links are now relative in references, making them clickable for quick navigation in IDEs.
TypeScript Native Previews
Last March, the TypeScript team unveiled their efforts to port the TypeScript compiler and toolset to native code. You can now use it as a Visual Studio Code extension, available here Due to being in the early stages of development, it required you to manually enable it after installing it either by:
- opening the Settings panel
- using the VS Code command palette
As Angular is not just about TypeScript, you will need a very large project to see the benefits.
Discover the whole announcement here.
Node.js
Unsupported Node.js versions
Node.js v18 reached End-of-Life on March 27, 2025, and will no longer be supported in Angular v20. Node.js versions 22.0 to 22.10 are also no longer supported.
Node.js supported versions
Before upgrading to Angular v20, ensure the Node.js version is at least 20.11.1. Angular updated its min Node.js support to 20.19, 22.12, and 24.0.
For the full list of supported versions per version, visit: https://angular.dev/reference/versions
Signals
Since the introduction of the Signals API in Angular v16 as a developer preview, each new version of Angular brought new features and improvements to it. The v20 release is no exception!
Resource API
The Resource API has been released in Angular 19:
- 19.0.0 for resource and rxResource
- 19.2.0 for httpResource
You can find a short introduction in official docs.
The v20 release only includes a few changes to it.
Equality function
The equality function is now supported by httpResource, as not previously passed to the underlying resource:
const usersResource = httpResource<User>(`/api/users/${this.id()}`, { equal: (a, b) => a.id === b.id,});
HTTP status code and headers fix
The HTTP status code and headers are now correctly returned when an HTTP request fails with an httpResource.
Stabilization
The following Signal APIs are now stable:
Nothing quite new on the API layer but stabilization means their adoption and usage will grow. If you have been waiting to create a derivated state with linkedSignal since its introduction as a developer preview in Angular 19, you can now use it safely!
Even if less known as being part of the Signals API as they do not expose Signals but using them under the hood, these APIs are now stable too:
- afterEveryRender, previously known as afterRender
- afterNextRender
- afterRenderEffect
Fixes
Always run root effects in the same order
Previously, the order in which root effects were executed was non-deterministic and relied on the order in which signal graph dirty notifications were propagated. Now root effects are always run in creation order.
Enhanced dynamic component creation
So far we were not able to set inputs and outputs for a dynamic component on its creation itself. After creating it only, we were able to set them in an imperative way:
export class App { vcr = viewChild('container', {read: ViewContainerRef}); componentRef?: ComponentRef<AlertNotification>;
createComponent(): void { this.vcr()?.clear(); this.componentRef = this.vcr.createComponent(AlertNotification);
this.componentRef.setInput('title', 'Something went wrong'); this.componentRef.?.instance.closed.subscribe(() => { this.componentRef?.destroy(); }); }}
Besides being very verbose, this approach did not include type-checking for inputs, making it error-prone.
The declarative way comes to the rescue
Angular 20 changes this in a declarative way, and enables defining two-way data and directive bindings too:
import { createComponent, signal, inputBinding, outputBinding, twoWayBinding,} from "@angular/core";
const canClose = signal(false);const value = signal("");
// Create MyDialogcreateComponent(MyDialog, { bindings: [ // Bind a signal to the `canClose` input. inputBinding("canClose", canClose),
// Listen for the `onClose` event specifically on the dialog. outputBinding<Result>("onClose", (result) => console.log(result)),
twoWayBinding("value", value), ], directives: [ // Apply the `FocusTrap` directive to `MyDialog` without any bindings. FocusTrap,
// Apply the `HasColor` directive to `MyDialog` // and bind the `red` value to its `color` input. // The callback to `inputBinding` is invoked on each change detection. { type: HasColor, bindings: [inputBinding("color", () => "red")], }, ],});
New Style Guide
Angular updates its old style guide for a modern version of it, more focused on Angular specific aspects and introducing changes due to modern APIs.
Discover the full version here: https://angular.dev/style-guide
Angular CLI
How new projects are affected by ng new
Default @angular/build
builder package
The @angular/build package is now used directly within all newly created projects and replaces the previous usage of the @angular-devkit/build-angular package. This has the advantage of removing the need to install all of the Webpack-related transitive dependencies contained within @angular-devkit/build-angular that are used to support the browser builder. This results in a significant reduction in both total dependency count and disk install size for new projects. New projects that would prefer to use the Webpack-based browser builder can still install the @angular-devkit/build-angular package within the workspace.
The @angular/build@19.2.0-next.2 package currently has a total unpacked size of ~115 MB. The @angular-devkit/build-angular@19.2.0-next.2 package currently has a total unpacked size of ~291 MB.
TypeScript module preserve option
Newly generated projects will now use the preserve value for the module option within the TypeScript configuration for the workspace (tsconfig.json). This value was added in TypeScript 5.4 and is intended to model the behavior of modern bundlers such as those used in the default application builder. This option value also has the advantage of automatically enabling esModuleInterop and setting moduleResolution to bundler which are the currently generated values for new projects. This allows explicit use of these options to be removed from the generated file. The resolveJsonModule option is also enabled with preserve which removes the need for developers to manually add it if JSON files are imported. JSON file imports are fully supported including unused property treeshaking with named imports in the application builder. Additional details on the option can be found here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#support-for-require-calls-in---moduleresolution-bundler-and---module-preserve
Removal of some defaulted build options
Some explicit options are removed from the angular.json for newly generated applications, as having a defaulted value. You can still set them if you need to: the removal reduces the total of the generated angular.json file. It affects:
- outputPath
- index
- browser
The scripts option is also removed as defined as an empty array by default, and can still be added if needed by a project.
{ "projects": { "my-app": { "architect": { "build": { "options": { "outputPath": "dist/my-app" "index": "src/index.html" "browser": "src/main.ts" "scripts": [] } } } } }}
Zoneless change detection
Zoneless change detection is now promoted to developer preview.
Angular 20 CLI ng new
command will prompt for new apps to be zoneless:
Do you want to create a 'zoneless' application without zone.js (DeveloperPreview)? (y/N)
To reduce the size of the initial angular.json for applications, newly generated zoneless applications will no longer contain an explicit empty polyfills option. The option can still be added and is available for use if needed by an application but the empty array value will no longer be present when generating a zoneless application. This has no effect on applications using Zone.js (default).
{ "projects": { "my-app": { "architect": { "build": { "options": { "polyfills": [] } } } } }}
TypeScript project references for libraries
When generating a project (via ng generate library), the created TypeScript configuration files (tsconfig.lib.json/tsconfig.spec.json) will be set up as composite projects and added as project references in the root tsconfig.json. This transforms the root tsconfig.json into a “solution” style configuration. This allows IDEs to more accurately discover and provide type information for the varying types of files (test, library, etc.) within each project. The Angular build process is otherwise unaffected by these changes.
{ "references": [ { "path": "./projects/my-lib/tsconfig.lib.json" }, { "path": "./projects/my-lib/tsconfig.spec.json" } ]}
Style guide ng generate
impacts
As part of the new style guide, the ng generate
command has been updated to not include suffixes anymore for Services, Components, and Directives.
You can pass a --type
option to specify the type of the generated file and keep the previous behavior: ng generate component product-list --type=component
.
It will result in a product-list.component.ts
file being created, including a ProductListComponent class.
Otherwise, the new default behavior would be a product-list.ts
file being created, including a ProductList class.
The value passed to the
--type
option is arbitrary and can be anything you want. The convention is to use the same value as the type of the file you are generating.
You can update your angular.json
file to keep the previous behavior:
"schematics": { "@schematics/angular:component": { "type": "component" }, "@schematics/angular:service": { "type": "service" }, "@schematics/angular:directive": { "type": "directive" }}
The style guide also affects Guards, Interceptors, Pipes, Resolvers, and Modules, but in a different way as they keep their suffix on their class name, but the file name changes.
The new default is to use a dash separator between the class name and the suffix. For example, auth.guard.ts
becomes auth-guard.ts
. You can pass the --typeSeparator
option to specify the separator character to use for the generated file.
For example, ng generate guard auth --typeSeparator=dot
will generate a auth.guard.ts
file instead of the new default auth-guard.ts
.
In the same way, you can update your angular.json
file to keep the previous behavior:
"schematics": { "@schematics/angular:guard": { "typeSeparator": "." }, "@schematics/angular:interceptor": { "typeSeparator": "." }, "@schematics/angular:module": { "typeSeparator": "." }, "@schematics/angular:pipe": { "typeSeparator": "." }, "@schematics/angular:resolver": { "typeSeparator": "." }}
Source map sources content
The sourceMap option now contains an additional sub-option named sourcesContent that allows the exclusion of original file content from generated source maps. This option affects both JavaScript and stylesheet source maps. The value of the sourcesContent option defaults to true.
Example usage to disable sources content:
{ "projects": { "my-app": { "architect": { "build": { "options": { "sourceMap": { "scripts": true, "styles": true, "sourcesContent": false } } } } } }}
Custom resolution conditions
When using the application build system, a new conditions option is now available that allows adding custom package resolution conditions that can adjust the resolution for conditional exports and imports. By default, the module and production/development conditions will be present with the latter dependent on the optimization option. If any custom conditions value is present including an empty array, none of these defaults will be present and must be manually included if needed. The following special conditions will always be present if their respective requirements are satisfied:
- es2015 (required by rxjs)
- es2020 (APF backwards compatibility)
- default
- import
- require
- node
- browser
For additional information regarding conditional exports/imports: https://nodejs.org/api/packages.html#conditional-exports https://nodejs.org/api/packages.html#subpath-imports
PWA
While not being under active development and not something the Angular team really plans on spending time on one way or the other in the near future, a change happens in Angular 20 to
consistently use withAppShell
for vite projects (as not used previously by Webpack-based builders).
Web workers versioning fix
When a new version of the app is available in a service worker, and a client with an old version exists, web workers initialized from a client with an old version will now be properly assigned to the same version. Before this change, a web worker was assigned to the newest version.
Remove background_color and theme_color from manifest
These properties are unnecessary and create additional maintenance overhead for developers.
// manifest.webmanifest{ "name": "My App", "short_name": "My App", "start_url": "/", "display": "standalone", "theme_color": "#000000", "background_color": "#000000"}
SSR
AngularNodeAppEngine
and AngularAppEngine
are now stable.
Header flushing
Header flushing was introduced to optimize response time, reducing time to first byte (TTFB) and improving perceived load times.
Server routing
First released in developer preview, the --server-routing
ng new option is now removed to always enable server routing when using the application builder.
provideServerRouting
is now removed from the SSR API. Setting the routes is now possible with withRoutes()
instead.
const serverConfig = new ApplicationConfig({ providers: [ provideServerRendering(withRoutes(routes)), provideServerRouting(routes), ],});
DOCUMENT token
Moves the DOCUMENT token from common into core since it’s relevant for lots of SSR use cases and users shouldn’t have to install common for it. The token is still exported through common for backwards compatibility.
import { DOCUMENT } from "@angular/common"; import { DOCUMENT } from "@angular/core";
Performance
Cleanups of multiple listeners
Multiple listeners/observers are cleaned up to improve performance. It covers multiple features in Angular: animations, router, event replay, pending tasks, and some others. That’s part of the Angular team effort at improving the performance on some aspects you might not have noticed!
Inputs and outputs caching
The set of inputs and outputs of a component is static, but the getter for the inputs and outputs property was re-computing them every time which the user might not expect. These changes add a couple of lines to cache them instead.
override get inputs() { return toInputRefArray(this.componentDef.inputs); this.cachedInputs ??= toInputRefArray(this.componentDef.inputs); return this.cachedInputs;}
override get outputs() { return toOutputRefArray(this.componentDef.outputs); this.cachedOutputs ??= toOutputRefArray(this.componentDef.outputs); return this.cachedOutputs;}
Language service
The language service now displays tags from the type definition, including, but not limited to, deprecation information from the API jsdoc.
Fatal diagnostics is now avoided for missing templates files, though a build will still fail in this case. However, for the language service, this allows the component to exist in the compiler registry and prevents cascading diagnostics within an IDE due to “missing” components. The originating template related errors will still be reported in the IDE. This case is particularly important when a template file either does not exist or is inaccessible to the language service.
Report more accurate diagnostic for invalid import
Currently, when an incorrect value is in the imports array, we highlight the entire array which can be very noisy for large arrays. This comes up semi-regularly (at least for me) when an import is missing.
These changes add some logic that reports a more accurate diagnostic location for the most common case where the imports array is static. Non-static arrays will fall back to the current behavior.
Extended diagnostic to warn when the @for track function is not NavigationCancellationCode
The Control flow feature comes as a replacement feature, @for
being used in place of *ngFor
.
With *ngFor
, the passed function was not called, just passing its reference:
<div *ngFor="let user of users; trackBy: trackByFn"></div>
With @for
, if you pass a function, it has to be called:
@for(user of users; track trackByRole(user)) {<div>{{user.name}}</div>}
The new change introduces a diagnostic to warn when the @for
track function is not called.
Fix autocompletion in HTML tags
Autocompletion happened wrongly in these situations:
- between the start and end HTML tags
- in the end tag
Templates
Tagged Template Literals
You can now simplify complex interpolation by avoiding concatenation in favor of template literals.
Firstly remember to use computed
when possible: that’s the most efficient way to avoid exposing complex expressions in your templates:
@Component({ template: ` <img [ngSrc]="'assets/images/' + imageName + '.jpg'" alt='profile image' /> <img [ngSrc]="profileImg()" alt='profile image' /> `})export class App { imageName = input(); profileImg = computed(() => `assets/images/${this.imageName()}.jpg`);}
But sometimes, some parts are not directly available from the component class, and you can now rely on template literals to do the job:
@Component({ template: ` @for (user of users(); track user.id) { <img ngSrc="{{\`assets/images/\${user.id}.jpg\`}}" alt="profile image" /> } `,})export class App { users = input<User[]>();}
You can even use TypeScript TemplateStringsArray to create a custom interpolation function:
@Component({ template: "{{ greet`Hello, ${name()}` }}",})export class MyComp { name = input();
greet(strings: TemplateStringsArray, name: string) { return strings[0] + name + strings[1] + "!"; }}
Expression operators support
Angular now supports the following expression operators: void, exponentiation, and in.
void operator
@Directive({ host: { '(mousedown)': 'void handleMousedown()' }})
It introduces a breaking change as if you have a property named void
and use it in expressions, you need to change the expression into {{this.void}}
.
exponentiation operator
@Component { template: '{{2 ** 3}}'}
in operator
export interface A { a: number}
export interface B { b: string}
export type MyUnionType = A | B;
item: MyUnionType = { a: 1 }
@if('a' in item) { // expecting item is no longer of type A | B, but only A <span>{{item.a}}</span> }
The in
support is part of a global issue at supporting TypeScript expressions.
Three expressions are still not supported:
optional chaining
: https://github.com/angular/angular/issues/34385pipe operator
: https://github.com/angular/angular/issues/27415arrow functions
: https://github.com/angular/angular/issues/14129
Support Sass package importers
Angular now supports Sass package importers, discover more on the official documentation.
Stop producing ng-reflect attributes by default
Angular does not produce ng-reflect
attributes by default anymore.
<div ng-reflect-foo="foo"></div>
For applications and test code relying on these attributes, Angular added a provideNgReflectAttributes()
function to enable the mode in which Angular would be producing those attributes (in dev mode only).
You can add this provider in your app.config.ts
file:
import { provideNgReflectAttributes } from '@angular/core';
const appConfig: ApplicationConfig = { providers: [ provideNgReflectAttributes() ]}
Forms
Fixes
These two changes fix some issues with forms:
- On multiple updates on a form using the
{emitEvent: false}
option, its statusChanges was not always emitted as expected. - If not created inside a FormGroup, FormControls submission/reset functions were not working.
New features
markAllAsDirty exposed on AbstractControl
The markAllAsDirty
method is now exposed on the AbstractControl
class and marks the control and all its descendants as dirty.
export class App { form = new FormGroup({ email: new FormControl(""), password: new FormControl(""), });
submit(): void { this.form.markAllAsDirty(); }}
Reset form without emitting events
The reset
method now accepts an optional emitEvent
parameter to control whether to emit events.
export class App { form = new FormGroup({ email: new FormControl(""), password: new FormControl(""), });
resetForm(): void { this.form.reset({ emitEvent: false }); }}
Common
Suspicious date format
The formatDate
function now throws an error for suspicious date patterns in date pipes: Y
(week-based year) without w (week of year), and D
(day of year) in the same pattern as M (month).
For example, the following code will throw an error:
// formatDate('2013-12-31', `YYYY/MM/dd`)
Suspicious use of week-based year "Y"
What does that mean exactly, you may ask? December 31st is the last day of the year and January 1st is the first day of the year. But these days can either (or both) be part of the last week of the previous year or the first week of the next year.
For example, January 2nd, 2010 is still part of the week 53 of 2009.
To avoid confusion while formatting correct ISO 8601 dates, the formatDate
function now throws an error if the format pattern is ambiguous.
Using Y
is fine in combination with w
(week of year), such as formatDate('2013-12-31', 'YYYY 'W'ww', 'en')
.
Http
Keepalive support for fetch requests
The HttpClient now supports the keepalive
option for fetch requests, from the Fetch API.
It requires you to use the withFetch
function to enable it and triggers a warning in devMode if you don’t.
export const appConfig: ApplicationConfig = { providers: [provideHttpClient(withFetch())],};
The keepalive option is particularly useful for:
- Beacon API-like functionality (logging analytics on page unload)
- Performance optimization for repeated requests to the same endpoint
- Better control over connection management in long-lived applications
import { HttpClient, HttpClientModule } from "@angular/common/http";
import { Injectable } from "@angular/core";
@Injectable({ providedIn: "root",})export class AnalyticsService { constructor(private http: HttpClient) {}
sendEvent(event: string) { return this.http.post( "https://api.example.com/analytics", { event }, { keepalive: true }, ); }}
Router
View Transitions typing
View Transitions support typing has been improved to include types for the transition and animation options. Learn more on developer chrome.
Route Guards typing
Remove ‘any’ type from route guards: https://github.com/angular/angular/pull/60378 (https://github.com/angular/angular/pull/60345)
relax required types on router commands
At the moment router requires a mutable array of commands (any[], e.g. on routerLink). However, the router does not need writable access to the array. Often I only have a readonly array, which I got via an input. I can not pass it to the router and therefore have to create a new mutable array out of it which requires additional code and creates runtime overhead.
routerlink as a custom element
routerlink
was not properly working with custom element anchor tags for Web Component library authors.
Now, If routerLink is used on a registered custom element, the static property/getter observedAttributes on the custom element class is checked for an href attribute. If href is part of observedAttributes treats the element as if it was a native <a>
element.
Abort a navigation
You can now programmatically abort a navigation.
This new feature was not requested by the community but was added to prepare the Angular framework integration with the current experimental Navigation API. The API enables better tracking of an ongoing navigation for SPAs and a site visitor can cancel a navigation by clicking the stop button in the browser.
There were some thoughts not to expose it as a public API but there were no specific needs at the end. This new function is a no-op if the navigation is beyond the point where it can be aborted.
export class App { private readonly router = inject(Router);
abortNavigation(): void { this.router.getCurrentNavigation()?.abort(); }}
If you listen to router.events
, an aborted navigation will be emitted as a NavigationCancel
event.
You can identify the reason of the cancellation through the code
property of the NavigationCancel
event.
A NavigationCancellationCode.Aborted
has been added to the API.
Asynchronous redirects
The route redirectTo
function can now return a Promise
or an Observable
, not only a string
or a synchronous function.
import { Router } from "@angular/router";
const routes = [ { path: ":id1/:id2", redirectTo: ({ params }) => { const tree = inject(Router).parseUrl( `/redirect?id1=${params["id1"]}&id2=${params["id2"]}` ); return of(tree); }, },];
Read resolver data ancestors
The resolved data from a parent route is now accessible in a child resolver. Previously, with child and parent routes, the resolved data from the parent was available in the child component but not in the child resolver.
provideRouter([ { path: "a", resolve: { aResolve: () => new Promise<string>((resolve) => setTimeout(() => resolve("a"), 5)), }, children: [ { path: "b", resolve: { bResolve: (route: ActivatedRouteSnapshot) => route.data["aResolve"] + "b", }, children: [{ path: "c", component: Empty }], }, ], },]);
Migrations changes
signal-queries-migration
It now keeps the accessibility modifier if it’s already present.
On such a snippet, running the migration would remove the public
modifier:
@ViewChild('sidenav', { read: MatSidenav }) public sidenav: MatSidenav;
It’s now fixed and the output now looks like this:
public readonly sidenav = viewChild('sidenav', { read: MatSidenav });
output-migration
If an output type is not void
, the emit
function takes a mandatory parameter such as :
save = output<User>();
saveUser(user: User) { this.save.emit(user);}
It was not enforced by @Output
feature.
By migrating, the script will now add a TODO comment for the developer to resolve this now mandatory parameter:
someChange = output<string>();
someMethod(): void { // TODO: The 'emit' function requires a mandatory string argument this.someChange.emit();}
If your @Output
was initialized without explicitly setting the type, the migration was not able to detect the type, resulting in:
@Output() save<User> = new EventEmitter();save = output();
The migration will now add the type to the output:
save = output<User>();
inject-migration
@Attribute
is now treated as optional by default, adding the {optional: true}
option on migration.
It also adds a !
if the nonNullableOptional
option of the migration script is set to true.
constructor(@Attribute('tabindex') private foo: string) {}
private foo = inject(new HostAttributeToken('tabindex')); private foo = inject(new HostAttributeToken('tabindex'), { optional: true })!;
A new internal-only option was added to determine whether the migration should replace constructor parameter references with this.param
property references.
It only applies to references to readonly properties in initializers.
// Before private foo;
constructor(readonly service: Service) { this.foo = service.getFoo(); }
// After readonly service = inject(Service); private foo = this.service.getFoo();
Other fixes also address handling shortand assignments in super call and super parameter referenced via this.
previous-style-guide [new]
When updating to Angular v20 via ng update
, a migration will be executed
that will add schematic generation (ng generate
) defaults to the workspace.
These defaults will ensure that existing projects will continue to generate
files as done in previous versions of the Angular CLI. All new projects (via ng new
) or
projects that do not explicitly contain these options in their workspace
will use the updated style guide naming behavior.
The option values for the schematics
field are as follows:
{ '@schematics/angular:component': { type: 'component' }, '@schematics/angular:directive': { type: 'directive' }, '@schematics/angular:service': { type: 'service' }, '@schematics/angular:guard': { typeSeparator: '.' }, '@schematics/angular:interceptor': { typeSeparator: '.' }, '@schematics/angular:module': { typeSeparator: '.' }, '@schematics/angular:pipe': { typeSeparator: '.' }, '@schematics/angular:resolver': { typeSeparator: '.' },}
inject-flags [new]
enum InjectFlags { Default: 0b0000 Host: 0b0001 Self: 0b0010 SkipSelf: 0b0100 Optional: 0b1000}
These changes remove the InjectFlags itself, deprecated since v14, as well as all the public API signatures that accepted it. I’ve included an automated migration from InjectFlags to the object literal.
Note that we still have internal usages of InjectFlags which will take more refactoring, but they’ve been replaced with the InternalInjectFlags which have the advantage of being a const enum.
BREAKING CHANGE:
InjectFlags has been removed. inject no longer accepts InjectFlags. Injector.get no longer accepts InjectFlags. EnvironmentInjector.get no longer accepts InjectFlags. TestBed.get no longer accepts InjectFlags. TestBed.inject no longer accepts InjectFlags.
https://github.com/angular/angular/pull/60318
replace-provide-server-rendering-import [new]
- Migrate imports of
provideServerRendering
from@angular/platform-server
to@angular/ssr
.
This commit introduces provideServerRendering
as the primary function for configuring server-side rendering, replacing provideServerRouting
. provideServerRendering
now includes the functionality of provideServerRouting
through the use of the withRoutes
feature.
This change consolidates server-side rendering configuration into a single, more flexible function, aligning with the evolution of Angular SSR.
Before:
import { provideServerRouting } from "@angular/ssr";import { serverRoutes } from "./app.routes";
provideServerRouting(serverRoutes);
After:
import { provideServerRendering, withRoutes } from "@angular/ssr";import { serverRoutes } from "./app.routes";
provideServerRendering(withRoutes(serverRoutes));
replace-provide-server-routing [new]
- Update
provideServerRendering
to usewithRoutes
and removeprovideServerRouting
from@angular/ssr
.
https://github.com/angular/angular-cli/commit/26fd4ea73ad2a0148ae587d582134c68a0bf4b86
update-module-resolution [new]
This commit adds a migration to update the TypeScript moduleResolution
option to 'bundler'
for improved compatibility with modern package resolution.
See: https://www.typescriptlang.org/tsconfig/#moduleResolution
https://github.com/angular/angular-cli/commit/1e137ca848839402bc214fbccdc04243862d01d0
use-application-builder
application migration should migrate karma builder package
The use-application-builder
update migration will now attempt to migrate
the karma
builder to use the @angular/build
package if no other
@angular-devkit/build-angular
usage is present.
https://github.com/angular/angular-cli/commit/8654b3fea4e2ba5af651e6c2a4afddaf6fc42802
control-flow
ng generate @angular/core:control-flow migration removes the container reference even if it is used in the component file https://github.com/angular/angular/pull/60210
Testing
TestBed.tick()
The TestBed.tick()
method, similarly to the ApplicationRef.tick(), synchronizes the state with the DOM. It can be used in unit tests to mimic the framework’s logic executed in production applications. The TestBed.tick() should be used instead of the deprecated TestBed.flushEffects().
Vitest
Add experimental vitest support for node and browser
npm i vitest jsdom --save-dev
Then update the angular.json
testing configuration to use vitest (replace the whole test
section):
"test": { "builder": "@angular/build:unit-test", "options": { "tsConfig": "tsconfig.spec.json", "buildTarget": "::development", "runner": "vitest" }}
Update your spec files to import the vitest syntax:
import { describe, it, expect } from "vitest";
describe("MyComponent", () => { it("should create", () => { expect(true).toBe(true); });});
You can also define specific in your angular.json:
"test": { "builder": "@angular/build:unit-test", "options": { "tsConfig": "tsconfig.spec.json", "buildTarget": "::development", "runner": "vitest", "reporters": ["junit"] }}
Find the complete reporters documentation on the vitest website.
When using the experimental unit-test
builder, the watch
option will
now default to true when there is an interactive terminal and not on CI.
This more closely aligns with the behavior of the existing karma
builder
and that of vitest itself. CI is determined by the presence of a truthy
CI
environment variable.
Browser testing support
The Vitest experimental support include browser testing but requires a few more steps to be taken:
First install the @vitest/browser
package:
npm i @vitest/browser --save-dev
You need to add a provider for your test, either PlayWright
or WebDriveIO
.
npm i playwright --save-dev
or
npm i webdriverio --save-dev
Then update your angular.json
to define the browsers you want to test on:
"test": { "builder": "@angular/build:unit-test", "options": { "tsConfig": "tsconfig.spec.json", "buildTarget": "::development", "runner": "vitest", "browsers": ["chromium"] }}
i18n
respect i18nDuplicateTranslation option when duplicates exist Running the extract-i18n command will always log duplicate i18n message IDs as warnings, regardless of the value of the i18nDuplicateTranslation option. With this fix, command will log duplication errors and exit with code 1 https://github.com/angular/angular-cli/commit/a42e045bab3bfbeb0bb69c3406fd0a76ae263cdf
Browser support
Angular will now log a warning in Devtools console for users to know if they are using a browser version outside of Angular’s supported browser set.
AI
Unrelated to this version, here are some references for you to get the best of AI tools to enhance your DevExperience: