Openin app 7
Medium = @ sexe» a
Get unlimited access to the best of Medium for less than $1/week. Become amember x
Announcing Angular v20
18minread - Draft
£ Minko Gechev
© listen (1) Share + More
‘The past couple of years have been transformative for Angular, as we've unleashed
major advancements like reactivity with Signals and the power of Zoneless
applications. We hope these features have helped the Angular community build the
next generation of web applications with fast time-to-market and robust
performance.
MN Angular”
And we are just getting started! Angular v20 is our latest release where we have
spent countless hours polishing some of our in-progress features for the rock-soliddeveloper experience that you deserve.
Some of the highlights:
Stabilizing APIs such as effect, linkedSignal, toSignal , incremental hydration,
route-level render mode config and promoting zoneless to developer preview
Improved debugging with Angular DevTools and partnering with Chrome for
custom Angular reporting directly in Chrome DevTools
Polishing developer experience with style guide updates, type checking and
language service support for host bindings, support for untagged template
literal expressions in templates, template hot module replacement by default,
and more.
Advancements in GenAI development with llms.txt and angular.dev guides and
videos for building Generative AI applications
Launching a request for comments for an official mascot for Angular
Promoting reactivity features to stable
We spent the past three years rethinking Angular’s reactivity model to make it more
robust and future-proof. In Angular v16 we shipped a developer preview of Angular
Signals and since then, they got a wide adoption in and outside of Google.
YouTube shared on stage how using Angular Signals with Wiz, they improved input
latency in Living Room with 35%. In the meantime, TC39 kicked off an investigation
to introduce Signals to the JavaScript language with reference implementation based
on Angular Signals.
After collecting feedback from RFCs and iterating on the implementation, we
promoted signal, computed, input and view queries APIs to stable. Today, we are
announcing effect, Linkedsignat and tosignat as stable as well.
New, experimental APIs
To tackle managing asynchronous state with Angular, in v19 we developed the
resource API. Since then, we introduced resource streaming and created a new API
called httpresource that allows you to make HTTP requests with a Signal-based
reactive API. Both of these APIs are available as part of v20 as experimental.The resource API allows you to initiate an asynchronous action when a signal
changes and expose the result of this action as a signal:
const userId: Signal
= getUserId();
const userResource = resource({
params: () => ({id: userd()}),
loader: ({request, abortSignal}): Promise => {
// fetch cancels any outstanding HTTP requests when the given ‘AbortSignal”
// indicates that the request has been aborted.
return fetch(*users/${request.id}’, {signal: abortSignal}) ;
s
ns
—=Z— EP >}
The code above will fetch the user with the particular identifier, when the usertd
signal changes.
Now let’s suppose we're getting data from a WebSocket. For this purpose we can use
a streaming resource:
@Component ({
template: *{{ dataStream.value() }}°
p
export class App {
// WebSocket initialization logic will Live here...
Wove
// Initialization of the streaming resource
dataStream = resource({
stream: () => {
return new Promise>>((resolve) => {
const resourceResult = signal<{ value: string] }>({
value: (1,
Ds
this.socket.onmessage = event => {
resourceResult.update(current => ({
value: [...current.value, event.data]
Ys
resolve(resourceResult) 5
Ys
tyDs
In this minimal example, we declare a new streaming resource, which returns a
promise of a signal. The signal has a value type ResourceStreantten , which
means that the signal can hold the value { value: string(] } or {error: .. } incase
we want to return an error.
We emit the values we receive over the WebSocket via the resourceResult signal.
Building on top of this pattern, we also shipped the experimental httpresource :
@Component ({
template: “{{ userResource.value() | json }}°
»
class UserProfile {
userId = signal(1);
userResource = httpResourcecUser>(() =>
“https: //example.com/v1/users/${this.userId()}°
Ds
+
The snippet above will send an HTTP GET request to the URL we specified every
time when the userId changes. httpResource returns HttpResourcekef which has a
value property of type signal that we can directly access in the template. The
userResource has other values, such as istoading, headers , and others, as well.
Under the hood, httpresource uses HttpClient so you can specify interceptors in the
HttpClient provider:
bootstrapApplication(AppComponent, {providers: [
provideHttpClient(
withInterceptors([loggingInterceptor, cachingInterceptor]),
I);Promoting Zoneless to developer preview
Over the past six months, we made a lot of progress in zoneless, specifically around
server-side rendering and error handling.
Many developers use Zone.js for capturing errors in their apps even without
realizing it. Zone.js also lets the framework know when we're ready to flush the
server-side rendered application to the client. In the world of zoneless, we had to
find robust solutions for these problems.
In v20 we now have a default handler for unhandtedrejection and uncaughtexception
in Node,js during SSR to prevent the node server from crashing in case of errors.
On the client, you can include provideBrowserGlobalérrorListeners in your providers.
You can start using zoneless today by updating your list of providers:
bootstrapAppLication(AppComponent, {providers: [
provideZonelesschangeDetection() ,
provideBrowserGlobalErrorListeners()
ns
In addition, make sure you remove the zone.js polyfill from your angular.json «
Learn more about the benefits of zoneless and how to transition your project in our
documentation.
If you’re creating a new Angular project, you can get it to be zoneless from the start
using the CLI:Solidifying Angular on the server
In v20 we also focused on polishing our flagship server-side rendering features —
incremental hydration and route-level rendering mode configuration. Today, we're
happy to promote both of them to stable!
As a reminder, incremental hydration makes your apps faster by downloading and
hydrating a portion of the page on a particular trigger. This way, your users don't
have to download all the JavaScript associated with a particular page, instead they
can gradually download only the parts that they need.
To start using incremental hydration today, configure hydration by specifying
withIncrementalHydration :
‘import { provideClientHydration, withIncrementalHydration } from '@angular/plat
provideClientHydration(withincrementalHydration());
———— >
In the templates of your components now you can use deferrable views:@defer (hydrate on viewport) {
+
That way, Angular will download the shopping cart component together with its
transitive dependencies and hydrate this part of the UI only when it enters the
viewport.
Additionally, you can now use route-level rendering mode configuration as a stable
API! If different routes in your app have different rendering requirements, you can
configure that in a server-route configuration:
export const routeConfig: ServerRoute = [
{ path: '/login', mode: RenderMode.Server },
{ path: '/dashboard', mode: RenderMode.Client },
{
path: '/product/:id',
mode: RenderMode.Prerender,
async getPrerenderParams() {
const dataService = inject (ProductService) ;
const ids = await dataService.getIds(); // ["1", "2", "3"]
J] “id? is used in place of *:id’ in the route path.
return ids.map(id => ({ id }))5
In the snippet above we configure to render the login page on the server, the
dashboard on the client, and prerender the product pages.
Notice that the product page requires an id parameter. To resolve the identifiers for
each product, we can use the asynchronous function get?rerenderParams . It returns
an object in which its keys map to router parameters. In the case of the /product/:id
page we return an object with an ia property.
You can host your server-side rendered apps with most cloud providers. We
partnered closely with Firebase App Hosting on a seamless deployment story thatsupports hybrid rendering (SSR, SSG, and CSR) and provides you the security and
scalability of Google Cloud.
Polishing developer experience
We spent a lot of time while developing v20 on engineering excellence — polishing
existing APIs to improve your developer experience. We did this across the board —
framework, router, forms, http, etc. Let me share more about the work we did here!
Performance insights in Chrome DevTools
To further enhance the developer experience and provide deeper insights into
application performance, we've collaborated with the Chrome DevTools team to
integrate Angular-specific profiling data directly into the Performance panel.
Previously, developers often had to switch between framework-specific profilers
and the browser’s DevTools, making it challenging to correlate information and
pinpoint bottlenecks, especially with minified production code. This new
integration aims to solve that by displaying Angular runtime data, such as
component rendering, change detection cycles, and event listener execution, within
the same timeline as other browser performance metrics.
This direct integration, available starting in Angular v20, leverages the Performance
panel extensibility API, specifically using the console.timestamp API for its low
overhead, ensuring that profiling doesn't negatively impact application
performance. Developers can now gain enhanced visibility into Angular’s internal
workings, with color-coded entries to distinguish between developer-authored
TypeScript code and Angular compiler-generated code. To enable this feature,
simply run the global utility ng.enabteProfiting() in your application or the
DevTools console. This advancement provides a more intuitive and comprehensive
performance analysis experience, empowering developers to build even more
performant Angular applications.eee neds -teioniee)
GR LB Console Clements sopleaon Sources _Perlormance Angusr Network A Memory Ustnouse m2 Oi
BOC 2s @ rarcan enhets Coaenery dC) Dara panies BO
i
Lerenm yao 26m
‘B CatapeeCampanert Template buton ek 50 Jstene
ona Whats row Notwarkcandions teint Search Alaieitance Prlacolmontor —Pelomance mentor
BO wm © Trw Oninublewe + 2hevee 2B
On the screenshot above, you can see this feature in action. Notice how at the
bottom of the performance timeline there's a track dedicated to Angular. With the
color-coded bars, you can preview component instantiation, running change
detection, etc. Both Angular DevTools and the Angular track in the Chrome
performance timeline use the same hooks with the difference that Chrome’s
performance timeline can put your app’s lifecycle into the context of other
JavaScript calls outside the framework.
In addition, the Angular track in Chrome’s performance timeline shows some data
that’s currently not present in Angular DevTools, such as component and provider
instantiation.
Framework additions and improvements
To dynamically create an Angular component you can use the createComponent
function. In v20 we introduce new features that let you apply directives and specifybindings to dynamically created components:
import {createComponent, signal, inputBinding, outputBinding} from '@angular/cc
const canClose = signal (false) ;
const title = signal('My dialog title’);
// 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('onClose', result => console. log(result)),
// Creates two way binding with the title property
twoWayBinding(‘title', title),
1,
directives: [
// Apply the ‘FocusTrap’ directive to ‘MyDialog’ without any bindings.
FocusTrap,
// Apply the “HasColor’ directive to ‘MyDialog’ and bind the ‘red’ value tc
// The callback to “inputBinding’ is invoked on each change detection.
t
type: HasColor,
bindings: [inputBinding(‘color', () => 'red")]
+
1
Ds
SD >
Above we create a dialog component and specify:
© canctose input binding, passing the signal canctose as a value
* Set the output onctose toa callback that logs the emitted result
* Two way binding between the title property and the title signal
Additionally, we add the rocustrap and Hascotor directives to the component.
Notice that we can also specify input bindings for the Hascotor directive that we
apply to MyDialog .
Extended template expression syntaxWe've been bridging the gap between Angular template expressions and full
JavaScript syntax to enable higher expressiveness and better developer experience.
Today, we're introducing support for the exponential operator +» and in operator:
{Ln 23}
{{ name in person }}
In v20 we also enable you to use untagged template literals directly in expressions:
Extended diagnostics
To guard against common errors, we introduced static checks that detect invalid
nullish coalescing, detection of missing imports for structural directives, and a
warning when you don't invoke the track function you've passed to efor :
@Component ({
template: *
Gfor (user of users; track trackFn) {