Angular Labs
COOKIES

What's New in Angular 20

Released on May 28th 2025

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:

host

You can enable it manually in your tsconfig.json file and it’s planned to be defaulted in v21 Angular projects:

tsconfig.json
{
"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:

Terminal window
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

Which command should you run to update your project to Angular v20?
npm install
ng update

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:

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.

What's the Signal Forms API status in Angular 20?
Experimental
Developer preview

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 MyDialog
createComponent(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.

angular.json
{
"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:

Terminal window
Do you want to create a 'zoneless' application without zone.js (Developer
Preview)? (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).

angular.json
{
"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:

angular.json
"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:

angular.json
"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:

angular.json
{
"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

What is the new default file name for a component?
Header.ts
header-component.ts
HeaderComponent.ts
header.ts

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.

server.config.ts
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.

common.ts
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.

language service

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.

language service

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.

language service


Extended diagnostic to warn when the @for track function is not NavigationCancellationCode

The Control flow feature comes as a replacement feature, @forbeing 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.

language service

Fix autocompletion in HTML tags

Autocompletion happened wrongly in these situations:

  • between the start and end HTML tags
  • in the end tag

language service

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:


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.

app.config.ts
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 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.Abortedhas 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 use withRoutes and remove provideServerRouting 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

Terminal window
npm i vitest jsdom --save-dev

Then update the angular.json testing configuration to use vitest (replace the whole test section):

angular.json
"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:

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:

Terminal window
npm i @vitest/browser --save-dev

You need to add a provider for your test, either PlayWright or WebDriveIO.

Terminal window
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:

angular.json
"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.