What's new in Angular 21.0?
Angular 21.0.0 is here!
The highlights? Signal Forms and zoneless change detection by default. The CLI, with Vitest becoming the default testing framework, also delivers on a long due improvement.
Let's dive in!
Signal Forms (experimental)
Angular 21 introduces a new experimental way to build forms: Signal Forms,
in the @angular/forms/signals package.
This signal-based approach provides better type safety and easier validation
compared to traditional reactive and template-driven forms.
private readonly credentials = signal({ email: '', password: '' });
protected readonly loginForm = form(
this.credentials,
// 👇 add validators to the fields, with their error messages
form => {
// email is mandatory
required(form.email, { message: 'Email is required' });
// must be a valid email
email(form.email, { message: 'Email is not valid' });
// password is mandatory
required(form.password, { message: 'Password is required' });
// should have at least 6 characters
minLength(form.password, 6, {
// can also be a function, if you need to access the current value/state of the field
message: password => `Password should have at least 6 characters but has only ${password.value().length}`
});
}
);
We wrote a two-part series to introduce you all the concepts of Signal Forms. The first one covers the basics, while the second one dives into more advanced topics like custom validation, custom form components, etc.
Check them out!
👉 Angular Signal Forms - Part 1
👉 Angular Signal Forms - Part 2
There is also an effort to make the migration to signal forms more progressive,
with the introduction of the @angular/forms/signals/compat package.
This package offers a compatForm function,
which is a version of the form function designed for backwards
compatibility with reactive forms by accepting reactive controls as a part of the data.
I'm not sure that this will save us from completely rewriting our forms,
but we appreciate the effort 🫡.
Zoneless by default 🚀
Angular applications are now zoneless by default!
provideZonelessChangeDetection() is no longer needed in new applications,
as Angular will use zoneless change detection automatically.
If your application relies on Zone.js,
you will need to explicitly add provideZoneChangeDetection() in the root providers.
A migration will automatically add it for you if necessary when updating to Angular v21.
The CLI now generates zoneless applications by default,
and you'll note that the component tests use await fixture.whenStable()
instead of fixture.detectChanges().
Core
The SimpleChanges type that we use for the ngOnChanges parameter is now generic
and can be type safe, if you add the type of your component as a type parameter.
To keep the backward compatibility,
it defaults to any when no type parameter is provided.
export class User implements OnChanges {
readonly name = input.required<string>();
// 👇 ngOnChanges is now type safe
ngOnChanges(changes: SimpleChanges<User>): void {
const nameChange = changes.name;
// typed as `SimpleChange<string> | undefined`
if (nameChange) {
console.log(`Name changed from ${nameChange.previousValue} to ${nameChange.currentValue}`);
}
}
Forms
In "classic" forms,
a new FormArrayDirective directive has been added,
allowing to have an array as the top element of a form.
Until now, you had to wrap it in a FormGroup and use the FormGroupDirective.
Http
The big change is that HttpClient is now provided in the root injector by default.
We no longer need to use provideHttpClient() in our applications,
except when we want to customize the HTTP client
(for example, to register interceptors).
It also simplifies the testing setup, where it was a bit verbose to have to use both
provideHttpClient() and provideHttpClientTesting().
We can now only use the latter.
The HttpClient also gained a few options:
responseTypehas been added to the Fetch version of the client (corresponding to the responsetypeproperty in the native fetch API, see the MDN docs)referrerPolicywhich can be used to specify the referrer information (see the MDN docs) has been added to the client (both the XMLHttpRequest and Fetch versions) and in theHttpResourceoptions.
Router
The router gained a new scroll option that can be used when navigating
to define the scrolling behaviour
(allowing to override the scroll restoration behaviour
that you can enable in provideRouter thanks to withInMemoryScrolling({ scrollPositionRestoration: 'enabled' })).
The scroll option can be 'manual' (no scrolling even if enabled globally)
or 'after-transition' (following the global behaviour).
So, using router.navigateByUrl('/', { scroll: 'manual' }) will prevent scrolling
even if scroll restoration is enabled globally.
Templates
The control flow migration will automatically be applied when updating to Angular v21, if you did not apply it yet.
Since the updated style guide written for
Angular v20,
it is now recommended to use class and style bindings
instead of the NgClass and NgStyle directives.
Angular v21 offers automatic migrations (optional) to help you with this change!
ng g @angular/core:ngclass-to-class-migration
ng g @angular/core:ngstyle-to-style-migration
Another optional migration has been added to migrate CommonModule imports
to the individual directives/pipes standalone imports:
ng g @angular/core:common-to-standalone
Speaking of changes in the templates,
RegExp are now supported:
you can now write something like <div>{{ /\\d+/.test(id()) }}</div> in your HTML files.
I'm not sure why anyone would want to do that, but well...
The @defer trigger on viewport also gained a new option,
allowing to define the options of the underlying
IntersectionObserver.
You can now write a @defer like this:
@defer (on viewport({ trigger, rootMargin: '50px' })) {
Compiler
In v21, the compiler now has its typeCheckHostBindings option enabled by default
(see our blog post about v20 to learn more about what it does).
A new diagnostic has also been added to detect when a component reads a required input/model/viewChild/contentChild property before it is initialized. This was already throwing an error at runtime, but now you will get a compile-time error instead.
Another diagnostic has been added to detect unreachable or duplicated or inefficient @defer triggers.
Styles encapsulation
A new strategy for view encapsulation has been added: ExperimentalIsolatedShadowDom.
As its name indicates, it is still experimental.
It leverages Shadow DOM to provide true encapsulation of styles,
but also isolates the component from the rest of the application.
Unlike ShadowDom encapsulation,
it prevents styles from leaking in or out of the component.
This is useful when you want to ensure that the component's styles
are completely isolated from the rest of the application,
including global styles.
There are still some issues with the implementation,
so it is not recommended for production use yet.
Vitest is the new default
Big change in the unit testing setup: Vitest is now the default testing framework when creating new applications with the CLI! 😲
Karma/Jasmine has been the default for a long time,
even though Karma has been deprecated for a while.
The experimental @angular/build:unit-test is now stable
and uses Vitest by default.
Some efforts have been made to simplify the configuration as much as possible,
and the minimal setup in angular.json is now:
"test": {
"builder": "@angular/build:unit-test"
}
You can see the difference in a generated project on angular-cli-diff. This little project that we maintain proves to be really useful to see what changed between CLI versions when you want to update your projects.
Even if the configuration is simpler, you can still customize it a lot with the following options,
either in angular.json or via CLI options when running ng test:
coverage(and all coverage related options, likecoverageInclude,coverageExclude,coverageThresholds, etc.) to configure code coveragebrowsersto define which browsers to use for testing. Note that Vitest stabilized the Browser Mode in v4, so you can now run tests in real browsers instead of jsdom/happy-dom if you want to. ThebrowserViewportoption allows to define the viewport size for browser tests.reportersto define which reporters to use, andoutput-fileto define where to output the report.setupFilesto define setup files to be run before the tests andprovidersFileto define a file that provides Angular testing providers.uiif you want to use Vitest's UI mode.watchto enable/disable watch mode (true by default when runningng test, but false in CI environments).filterto run only tests matching a specific pattern in their test suite or test name.list-teststo list all the tests without running them (useful to check what theinclude/excludeoptions are doing).
The CLI team also added a runnerConfig option
to allow using a custom Vitest configuration file,
if you need to customize something not covered by the CLI options.
For example, if you want to enable Vitest isolation mode (disabled by default in the CLI as it slows down tests),
you can add a vitest-base.config.ts file to your project using: ng g config vitest.
Then customize it like this:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
isolate: true,
},
});
We really like Vitest and its Browser Mode that allows to use real browsers
and write more expressive tests using its Locator API.
We wrote a dedicated blog post to explain how to
write more expressive than ever component unit tests 🤩:
👉 Angular tests with Vitest browser mode
It is still possible to use Karma if you want to,
by setting the runner option to "karma".
And you can generate a new project with Karma as the default
by using ng new my-app --test-runner=karma.
A migration is also available to migrate existing tests from Jasmine to Vitest:
ng generate refactor-jasmine-vitest
This migration is fairly mechanical, so you'll probably have to manually adjust some tests afterward. But it should save you some time, as it replaces all Jasmine-specific functions, types, matchers and imports with their Vitest equivalents! It does not migrate the test setup or dependencies though, so you'll have to do that part manually.
The migration has some useful options:
--add-importsto automatically add the Vitest imports in your test files (if you don't want to use global imports, which is the default when generating a new application)--file-suffixif you use a different suffix than.spec.tsfor your test files--includeto target only specific files or folders
Note that fakeAsync tests need to be rewritten,
as fakeAsync relies on a patched Zone.js for Jasmine,
and there is no version of it for Vitest.
The migration will also add helpful TODO comments in your tests
to indicate where it couldn't automatically migrate them.
Vitest is definitely a great addition to Angular,
and the way to go if you start a new project!
The web-test-runner and jest experimental builders
which were introduced as a possible alternative to Vitest
will be removed in v22.
ng serve
It is now possible to define variables that will be replaced
during the serve process,
using the define option in the serve configuration.
For example, you can define a VERSION variable
that will be replaced with the version of your application
from the command line (making it simple to use environment variables):
ng serve --define VERSION="'1.0.0'"
and then use it in your code:
// @ts-expect-error defined with --define flag
console.log(`App version: ${VERSION}`);
This was already possible since v17.2 in builds,
but is now also available for ng serve.
Tailwind schematic
A new schematic has been added to easily set up Tailwind CSS in your Angular projects. You can use it when creating a new application:
ng new my-app --style tailwind
This sets up Tailwind CSS with a minimal .postcssrc.json configuration file
and adds the required dependencies (tailwindcss, @tailwindcss/postcss, and postcss).
Interestingly, ng add now supports adding a package that is not a schematic
and will fall back to installing the NPM package
and then try to run its schematic if it has one.
So, you can also add Tailwind CSS to an existing project with:
ng add tailwindcss
Style guide 2016
If you're nostalgic about the old Angular file naming conventions from 2016,
you can now generate a project following its recommendations
by using the --file-name-style-guide=2016 option when creating a new application.
This option adds a retro vibe to your project with file names like user.component.ts,
user.pipe.ts, and user.service.ts 🪩.
The component, directive, and services name will also follow the 2016 conventions,
with a Component, Directive, or Service suffix in their class names.
ng version
The ng version command has been improved to provide more detailed information
about the packages in your Angular project (showing the requested and installed versions).
A --json option has also been added to output the information in JSON format,
which can be useful for automation scripts.
Accessibility
A new @angular/aria package has been introduced
to provide headless and accessible directives that implement common ARIA patterns.
They handle keyboard interactions, ARIA attributes, focus management, and screen reader support.
These directives are built on top of the CDK and developed by the Angular Material team.
The package is in developer preview in v21
and currently contains the following directives: Accordion, Autocomplete, ComboBox, Grid, ListBox, Menu, Multiselect, Select, Tabs, Toolbar, and Tree.
We haven't tested them yet,
so we'll let you explore them on your own.
AI
The AI section is becoming a regular section of our release review! The CLI team worked hard on the MCP server. It now offers 7 tools:
list_projects: lists the Angular projects in the workspaceget_best_practices: provides the latest best practicesfind_examples(now stable): finds code examples for a given topicsearch_documentation: searches the Angular documentationmodernize(still experimental): suggests modernizations for your codebaseai_tutor(new): provides interactive learning to build a "Smart Recipe Box" applicationonpush_zoneless_migration(new): helps migrate to OnPush and zoneless change detection
find_examples is now stable,
and has been improved to support filtering, weighted results,
and searching for specific Angular packages and versions.
The examples you add to your project can now have a frontmatter description,
with a title, summary and keywords.
It can also be marked as experimental
if they use experimental APIs.
By default, the database has just one example for a @if,
but we can expect the default database to grow over time.
search_documentation has also been improved to search the documentation
for the specific Angular version of your project.
modernize is still experimental,,
and now can run migrations directly.
So if you ask your favorite AI assistant
to modernize a component,
the MCP server will be able to run the necessary migrations
to update your codebase (control-flow, signal migrations, etc.).
onpush_zoneless_migration is an interesting new tool
that analyzes your codebase and provides a step-by-step plan
to migrate your application to OnPush and zoneless.
It targets components and their tests,
helps you identify ZoneJS usages that need to be addressed
and suggests how to migrate to zoneless.
Summary
The transition to zoneless by default, the introduction of Signal Forms and Vitest as the new default testing framework are big news for Angular developers.
Migration of existing applications to turn on zoneless change detection will require a big effort though. As well as for Vitest if you have thousands of Jasmine tests like we do.
Signal forms have still some rough edges as they are still experimental, but they look very promising, and are maybe a good choice for new projects? We will keep an eye on their evolution in the coming releases, and let you know what we think about them.
Stay tuned!
All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!
← Article plus ancien
Angular tests with Vitest browser mode
Étiquettes
Nos livres en vente


Prochaines sessions de formation
- Du 1 au 4 déc. 2025Vue : de Zéro à Ninja(à distance)
- Du 8 au 11 déc. 2025Angular : de Zéro à Ninja(à distance)
- Du 19 au 22 janv. 2026Angular : de Ninja à Héros(à distance)
- Du 9 au 12 févr. 2026Vue : de Zéro à Ninja(à distance)
- Du 2 au 5 mars 2026Angular : de Zéro à Ninja(à distance)
