{"id":33917,"date":"2026-06-03T21:29:57","date_gmt":"2026-06-03T19:29:57","guid":{"rendered":"https:\/\/www.angulararchitects.io\/?p=33917"},"modified":"2026-06-03T22:49:54","modified_gmt":"2026-06-03T20:49:54","slug":"angular-22-the-most-important-new-features-at-a-glance","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/","title":{"rendered":"Angular 22: The Most Important New Features at a Glance"},"content":{"rendered":"<p>With Angular 22, several central Signal APIs leave their experimental status: the <strong>Resource API<\/strong> and <strong>Signal Forms<\/strong> are now stable and ready for production use. In parallel, <strong><code>OnPush<\/code> becomes the new default change detection strategy<\/strong>, and the framework gains more ergonomic building blocks for modern, reactive applications with <code>@Service<\/code>, <code>injectAsync<\/code>, and <code>debounced<\/code>.<\/p>\n<p>This article highlights the most important innovations in Angular 22 using concrete examples and also picks up selected highlights from Angular 21.1 and 21.2.<\/p>\n<p>\ud83d\udcc2 <a href=\"https:\/\/github.com\/angular-architects\/flights42\/tree\/ng22\">Source Code<\/a> (Branch <em>ng22<\/em>)<\/p>\n<h2>OnPush as the New Default Change Detection Strategy<\/h2>\n<p>A decision that has been discussed in the Angular community since the very beginning is now reality: <code>OnPush<\/code> is the new default change detection strategy. This shift makes perfect sense with the rise of Signals and Zone-less Angular: anyone using Signals receives precise notifications about changes, and <code>OnPush<\/code> takes optimal advantage of them. The result is a performant change detection that focuses specifically on the components affected by changes.<\/p>\n<p>If you need the previous behavior, set the strategy manually to <code>Eager<\/code>:<\/p>\n<pre><code class=\"language-ts\">import { ChangeDetectionStrategy, Component } from &#039;@angular\/core&#039;;\n\n@Component({\n  selector: &#039;app-legacy&#039;,\n  changeDetection: ChangeDetectionStrategy.Eager,\n  template: `...`\n})\nexport class LegacyCmp { [...] }<\/code><\/pre>\n<p>The <code>Eager<\/code> setting replaces the original <code>Default<\/code> setting, which is now deprecated. In this case, change detection checks the entire component tree for changes.<\/p>\n<p>To avoid breaking changes, <code>ng update<\/code> activates the <code>Eager<\/code> option when updating the Angular version if <code>OnPush<\/code> was not explicitly set.<\/p>\n<h2>Resource API Becomes Stable in Angular 22<\/h2>\n<p>The Resource API has so far been the missing link in the Signal ecosystem: it enables reactive, asynchronous derivation of data, typically HTTP requests that are triggered after Signal changes. Despite its central role, it remained in experimental status for a long time. That changes abruptly with Angular 22: <code>resource<\/code>, <code>rxResource<\/code>, and <code>httpResource<\/code> are now stable and can be safely used in production.<\/p>\n<p>The most convenient entry point is the <code>httpResource<\/code> function. It receives a lambda expression that returns an HTTP request. This expression is reactive: if a Signal used within it changes, the request is automatically restarted.<\/p>\n<pre><code class=\"language-ts\">import { httpResource } from &#039;@angular\/common\/http&#039;;\nimport { ChangeDetectionStrategy, Component, signal } from &#039;@angular\/core&#039;;\nimport { Flight } from &#039;..\/..\/data\/flight&#039;;\n\n@Component({\n  selector: &#039;app-flight-search&#039;,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  [...]\n})\nexport class FlightSearch {\n  protected readonly filter = signal({ from: &#039;Hamburg&#039;, to: &#039;Graz&#039; });\n\n  protected readonly flightsResource = httpResource&lt;Flight[]&gt;(\n    () =&gt; ({\n      url: &#039;https:\/\/demo.angulararchitects.io\/api\/flight&#039;,\n      params: {\n        from: this.filter().from,\n        to: this.filter().to,\n      },\n    }),\n    { defaultValue: [] },\n  );\n\n  protected readonly flights = this.flightsResource.value;\n  protected readonly error = this.flightsResource.error;\n  protected readonly isLoading = this.flightsResource.isLoading;\n\n  protected search(): void {\n    this.flightsResource.reload();\n  }\n}<\/code><\/pre>\n<p>The type parameter <code>Flight[]<\/code> defines the expected response type. The <code>defaultValue<\/code> argument ensures that the component is not confronted with <code>undefined<\/code> at startup. If the resource should not trigger a request, it is sufficient to return <code>undefined<\/code>:<\/p>\n<pre><code class=\"language-ts\">protected readonly flightsResource = httpResource&lt;Flight[]&gt;(\n  () =&gt; {\n    const filter = this.filter();\n    if (!filter.from || !filter.to) {\n      return undefined;\n    }\n    return {\n      url: &#039;https:\/\/demo.angulararchitects.io\/api\/flight&#039;,\n      params: { from: filter.from, to: filter.to },\n    };\n  },\n  { defaultValue: [] },\n);<\/code><\/pre>\n<p>The resource manages its state via Signals: <code>value<\/code> contains the loaded data, <code>error<\/code> provides error information, and <code>isLoading<\/code> indicates the loading state. In addition, the resource knows a more detailed <code>status<\/code> with the values <code>idle<\/code>, <code>loading<\/code>, <code>reloading<\/code>, <code>error<\/code>, <code>resolved<\/code>, and <code>local<\/code> (for locally overridden values).<\/p>\n<p>In the template, these signals can be used directly, for instance to display the loading state and to iterate over the loaded data:<\/p>\n<pre><code class=\"language-html\">@if (flightsResource.isLoading()) {\n  &lt;div&gt;Loading ...&lt;\/div&gt;\n}\n\n@if (flightsResource.error()) {\n  &lt;div&gt;Error: {{ flightsResource.error() }}&lt;\/div&gt;\n} @else {\n  &lt;div class=&quot;row&quot;&gt;\n    @for (flight of flightsResource.value(); track flight.id) {\n      &lt;app-flight-card [item]=&quot;flight&quot; \/&gt;\n    }\n  &lt;\/div&gt;\n}<\/code><\/pre>\n<p>Race conditions are handled automatically: if multiple requests arrive in quick succession, only the result of the most recent one is used and older ones are cancelled where still possible. This matches the behavior of <code>switchMap<\/code> in RxJS.<\/p>\n<h2>Incremental Hydration Now Enabled by Default<\/h2>\n<p>With Angular 22, <code>provideClientHydration()<\/code> enables Incremental Hydration automatically. If you don't need it, you disable it explicitly with the new <code>withNoIncrementalHydration()<\/code> feature. A bundled schematic migration helps with the update.<\/p>\n<h2>Signal Forms Becomes Stable and Production-Ready<\/h2>\n<p>Signal Forms can now also be used in production. The path from the experimental API to the stable version was remarkably fast. This was made possible through extensive internal case studies at Google, in which typical form applications were systematically examined.<\/p>\n<p>The heart of Signal Forms is the <code>form<\/code> function. It receives a Signal containing the form data as well as an optional schema with validation rules:<\/p>\n<pre><code class=\"language-ts\">import { linkedSignal } from &#039;@angular\/core&#039;;\nimport { form, minLength, required } from &#039;@angular\/forms\/signals&#039;;\n\n@Component({ [...] })\nexport class FlightEdit {\n  private readonly store = inject(FlightDetailStore);\n\n  protected readonly flight = linkedSignal(() =&gt;\n    normalizeFlight(this.store.flight()),\n  );\n\n  protected readonly flightForm = form(this.flight, (path) =&gt; {\n    required(path.from);\n    required(path.to);\n    required(path.date);\n    minLength(path.from, 3);\n  });\n}<\/code><\/pre>\n<p>The result is a <code>FieldTree<\/code>: a deeply nested Signal structure in which each property is represented as a Signal with form status (<code>value<\/code>, <code>dirty<\/code>, <code>invalid<\/code>, <code>errors<\/code>). For template binding, the <code>FormField<\/code> directive is used:<\/p>\n<pre><code class=\"language-html\">&lt;input [formField]=&quot;flightForm.from&quot; id=&quot;flight-from&quot; \/&gt;\n&lt;div&gt;{{ flightForm.from().errors() | json }}&lt;\/div&gt;<\/code><\/pre>\n<h2>New <code>@Service<\/code> Decorator: Register Services More Concisely<\/h2>\n<p>A striking new addition is the new <code>@Service<\/code> decorator. It replaces the common cases in which you previously wrote <code>@Injectable()<\/code> or <code>@Injectable({ providedIn: &#039;root&#039; })<\/code>, and is a better match for the actual intent of providing a service:<\/p>\n<pre><code class=\"language-ts\">import { Service } from &#039;@angular\/core&#039;;\n\n@Service()\nexport class FlightClient { [...] }<\/code><\/pre>\n<p>By default, the service is provided in the root scope. If you don't want that, you can provide the service manually instead, for example in <code>app.config.ts<\/code>, at the component level, or at the route level. In this case, you set <code>autoProvided<\/code> to <code>false<\/code>:<\/p>\n<pre><code class=\"language-ts\">@Service({ autoProvided: false })\nexport class TabRegistry { [...] }<\/code><\/pre>\n<h2><code>injectAsync<\/code>: Inject Services Lazily<\/h2>\n<p>With <code>injectAsync<\/code>, dependencies can be injected lazily, that is, only when they are actually needed. This is especially useful for services that load extensive libraries and are only required upon a specific user action.<\/p>\n<pre><code class=\"language-ts\">import { injectAsync } from &#039;@angular\/core&#039;;\n\n@Component({ [...] })\nexport class CheckinPage {\n  private readonly upgradeService = injectAsync(() =&gt;\n    import(&#039;.\/upgrade-service&#039;).then((m) =&gt; m.UpgradeService),\n  );\n\n  protected async upgrade(): Promise&lt;void&gt; {\n    const flightNumber = this.checkinFormModel().ticketId;\n    const upgradeService = await this.upgradeService();\n    upgradeService.upgrade(flightNumber);\n  }\n}<\/code><\/pre>\n<p>The <code>injectAsync<\/code> function receives a lambda expression that returns a Promise. The result is a function whose first call takes care of loading the service. The import of <code>UpgradeService<\/code>, and thus the loading of the associated bundle, only happens on the first call to <code>upgrade()<\/code>.<\/p>\n<p>For lazy loading to work, the injected service must be auto-provided, that is, either decorated with <code>@Injectable({ providedIn: &#039;root&#039; })<\/code> or with the new <code>@Service()<\/code> decorator.<\/p>\n<h2>Prefetching with <code>injectAsync<\/code> and <code>onIdle<\/code><\/h2>\n<p>Lazy loading inevitably means a delay on the first call. To avoid this, the bundle can be loaded in advance.<\/p>\n<p>For exactly this purpose, <code>injectAsync<\/code> offers the <code>prefetch<\/code> option: it points to a function that returns a Promise. As soon as the Promise resolves, Angular begins loading the service.<\/p>\n<p>This mechanism can be used together with the helper function <code>onIdle<\/code>. It returns a Promise and resolves it as soon as the browser has nothing to do:<\/p>\n<pre><code class=\"language-ts\">import { injectAsync, onIdle } from &#039;@angular\/core&#039;;\n\nprivate readonly upgradeService = injectAsync(\n  () =&gt; import(&#039;.\/upgrade-service&#039;).then((m) =&gt; m.UpgradeService),\n  { prefetch: onIdle },\n);<\/code><\/pre>\n<p>Internally, <code>onIdle<\/code> delegates to <a href=\"https:\/\/developer.mozilla.org\/docs\/Web\/API\/Window\/requestIdleCallback\"><code>requestIdleCallback<\/code><\/a> and falls back to <code>setTimeout<\/code> if the browser doesn't support this API. If needed, a timeout can be configured after which prefetching will be triggered at the latest:<\/p>\n<pre><code class=\"language-ts\">injectAsync(\n  () =&gt; import(&#039;.\/upgrade-service&#039;).then((m) =&gt; m.UpgradeService),\n  { prefetch: () =&gt; onIdle({ timeout: 100 }) },\n);<\/code><\/pre>\n<p>If you want to adjust the behavior project-wide, you can swap out the underlying <code>IdleService<\/code> via <code>provideIdleServiceWith<\/code>, typically in <code>app.config.ts<\/code>.<\/p>\n<h2>Resource Composition via Snapshots<\/h2>\n<p>A fundamental principle of reactive programming is deriving values from one another. A Computed is created from one or more Signals and updates as soon as its sources change. With Resources, this kind of derivation was previously only possible indirectly: you could project their individual signals (<code>value<\/code>, <code>error<\/code>, <code>isLoading<\/code>), but not the Resource as a whole. Since Angular 21.2, there is a dedicated concept for this: using so-called snapshots, a Resource can be fully transformed into a new Resource without touching the original loading logic.<\/p>\n<p>The starting point is the <code>snapshot<\/code> signal of a Resource, which contains the complete current state including status and value. A snapshot therefore provides the entire state as an object with Signals. This object can be mapped to a new object with Signals derived from it. From this derived snapshot, <code>resourceFromSnapshots<\/code> in turn builds a new Resource.<\/p>\n<p>For illustration, consider an example that filters the loaded data, for instance to display only luggage items above a certain minimum weight:<\/p>\n<pre><code class=\"language-ts\">import {\n  linkedSignal,\n  Resource,\n  resourceFromSnapshots,\n  ResourceSnapshot,\n  Signal,\n} from &#039;@angular\/core&#039;;\n\nexport function withMinWeight(\n  input: Resource&lt;Luggage[]&gt;,\n  minWeight: Signal&lt;number&gt;,\n): Resource&lt;Luggage[]&gt; {\n  const derived = linkedSignal&lt;\n    { snap: ResourceSnapshot&lt;Luggage[]&gt;; min: number },\n    ResourceSnapshot&lt;Luggage[]&gt;\n  &gt;({\n    source: () =&gt; ({ snap: input.snapshot(), min: minWeight() }),\n    computation: ({ snap, min }) =&gt; {\n      if (snap.status === &#039;resolved&#039;) {\n        return { ...snap, value: snap.value.filter((item) =&gt; item.weight &gt;= min) };\n      }\n      return snap;\n    },\n  });\n\n  return resourceFromSnapshots(derived);\n}<\/code><\/pre>\n<p>Via the <code>computation<\/code> callback of the <code>linkedSignal<\/code>, you decide how the new snapshot is composed from the source signals. If either the source resource or the <code>minWeight<\/code> signal changes, the computation is automatically re-executed. The derived resource therefore always remains consistent with its inputs.<\/p>\n<p>The pattern can be used generically for arbitrary transformations. An interesting use case, which the Angular team also describes alongside snapshots, is to keep the most recently loaded value during reloading instead of displaying <code>undefined<\/code>:<\/p>\n<pre><code class=\"language-ts\">export function withPreviousValue&lt;T&gt;(input: Resource&lt;T&gt;): Resource&lt;T&gt; {\n  const derived = linkedSignal&lt;ResourceSnapshot&lt;T&gt;, ResourceSnapshot&lt;T&gt;&gt;({\n    source: input.snapshot,\n    computation: (snap, previous) =&gt; {\n      if (snap.status === &#039;loading&#039; &amp;&amp; previous?.value?.status === &#039;resolved&#039;) {\n        return { ...snap, value: previous.value.value };\n      }\n      return snap;\n    },\n  });\n\n  return resourceFromSnapshots(derived);\n}<\/code><\/pre>\n<p>The <code>previous<\/code> argument of the <code>computation<\/code> callback contains the most recently produced snapshot of the derived resource. This way, the previous value can be selectively carried over into the new snapshot.<\/p>\n<h2><code>debounced<\/code>: Debouncing for Signals and Resources<\/h2>\n<p>By nature, Signals know nothing of time: unlike Observables, they therefore have no concept of delay or throttling. This has so far made debouncing in pure Signal chains impossible, at least if you don't want to break this mental model.<\/p>\n<p>For forms, the Angular team has therefore built debouncing directly into Signal Forms, which covers a large part of the use cases. For everything beyond that, there is now the <code>debounced<\/code> function.<\/p>\n<p>Unlike Signals, Resources are well aware of time. This is exactly where <code>debounced<\/code> comes in. The function creates a <code>Resource<\/code> whose value is updated with the specified delay. The <code>status<\/code> of the resource indicates whether the value is still within the pending window:<\/p>\n<pre><code class=\"language-ts\">import { debounced } from &#039;@angular\/core&#039;;\n\nconst filter = signal(&#039;&#039;);\nconst debouncedFilter = debounced(filter, 300); \/\/ 300ms\n\neffect(() =&gt; console.log(debouncedFilter.value()));<\/code><\/pre>\n<h2><code>FormRoot<\/code> and Submission API in Signal Forms<\/h2>\n<p>Since Angular 21.2, the Submission API for Signal Forms has been available. It allows you to define the entire logic for submitting a form directly in the call to <code>form<\/code>:<\/p>\n<pre><code class=\"language-ts\">import { FormRoot, submit } from &#039;@angular\/forms\/signals&#039;;\n\n@Component({\n  imports: [ [...], FormRoot ],\n  [...]\n})\nexport class FlightEdit {\n  protected readonly flightForm = form(this.flight, flightSchema, {\n    submission: {\n      action: async (form) =&gt; this.save(form),\n      ignoreValidators: &#039;none&#039;,\n      onInvalid: (form) =&gt; this.reportValidationError(form),\n    },\n  });\n}<\/code><\/pre>\n<p>The <code>action<\/code> property contains the asynchronous save logic and can return server-side validation errors as its return value, which Signal Forms then takes directly into the form state. The <code>ignoreValidators<\/code> option controls whether failing or still pending validators block the submission (<code>none<\/code> | <code>pending<\/code> | <code>all<\/code>). <code>onInvalid<\/code> is called when validation prevents submission.<\/p>\n<p>In the template, the <code>FieldTree<\/code>, which represents the entire form, is bound to the <code>form<\/code> tag via the new <code>FormRoot<\/code> directive. This directive takes care of three tasks: it suppresses the browser's default validation behavior (such as the native tooltips on <code>required<\/code> fields), connects the <code>action<\/code> from the submission configuration with the <code>submit<\/code> event of the form, and prevents duplicate validation messages. To trigger the <code>submit<\/code> event, an ordinary <code>button<\/code> without an explicit <code>type<\/code> attribute is sufficient. Within a form, it acts as a submit button by default:<\/p>\n<pre><code class=\"language-html\">&lt;form [formRoot]=&quot;flightForm&quot;&gt;\n  [...]\n  &lt;button&gt;Save&lt;\/button&gt;\n&lt;\/form&gt;<\/code><\/pre>\n<p>If additional submission actions are needed, e.g. for an approval workflow, the <code>submit<\/code> helper function helps, which only executes the submission if the form is valid:<\/p>\n<pre><code class=\"language-ts\">import { submit } from &#039;@angular\/forms\/signals&#039;;\n\nprotected async requestApproval(): Promise&lt;void&gt; {\n  await submit(this.flightForm, {\n    action: async (form) =&gt; {\n      await this.store.requestApproval(form().value());\n    },\n    ignoreValidators: &#039;none&#039;,\n    onInvalid: (form) =&gt; this.reportValidationError(form),\n  });\n}<\/code><\/pre>\n<p>The <code>onInvalid<\/code> handler is also well suited for automatically setting focus to the first invalid input field after a failed validation. Signal Forms provides the <code>focusBoundControl()<\/code> method for this:<\/p>\n<pre><code class=\"language-ts\">private reportValidationError(form: FieldTree&lt;Flight&gt;): void {\n  this.snackBar.open(&#039;Please correct the validation errors&#039;, &#039;OK&#039;);\n  const errors = form().errorSummary();\n  if (errors.length &gt; 0) {\n    errors[0].fieldTree().focusBoundControl();\n  }\n}<\/code><\/pre>\n<p><div style=\"\nmargin: 8px 0;\npadding: 22px;\nborder: 1px solid #e5e7eb;\nborder-radius: 14px;\nbackground: #f8fafc;\n\">NOTE<\/p>\n<h3 style=\"margin-top:0\">Modern Angular<\/h3>\n<\/p>\n<p style=\"margin:0 0 12px 0\"><strong style=\"display:inline-block; background:#16a34a; color:#fff; padding:4px 10px; border-radius:999px; font-size:0.85em\">\u2713 Already updated to Angular 22!<\/strong><\/p>\n<p>You'll find more on Signal Forms and modern Angular architecture in my new eBook Modern Angular. It covers Signals, architecture, testing, AI assistants, and practical solutions for modern business applications.<\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/modern-book\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/01\/cover-klein.png\" width=\"400\" alt=\"Modern Angular - Signal-first, Architecture-first, Practice-first\" style=\"cursor:pointer !important\"><\/a><\/p>\n<p><a style=\"cursor:pointer !important\" href=\"https:\/\/www.angulararchitects.io\/modern-book\">More about the book \u2192<\/a>\n<\/div>\n<\/p>\n<h2>CSS Classes for Signal Forms<\/h2>\n<p>Since Angular 21.2, Signal Forms supports conditional CSS formatting, as you know it from Template-driven and Reactive Forms. Via <code>provideSignalFormsConfig<\/code>, you first define a mapping of CSS class names to form status predicates:<\/p>\n<pre><code class=\"language-ts\">import { provideSignalFormsConfig } from &#039;@angular\/forms\/signals&#039;;\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    [...],\n    provideSignalFormsConfig({\n      classes: {\n        &#039;ng-invalid&#039;: field =&gt; field.state().invalid(),\n        &#039;ng-valid&#039;:   field =&gt; field.state().valid(),\n        &#039;ng-dirty&#039;:   field =&gt; field.state().dirty(),\n        &#039;ng-pristine&#039;: field =&gt; !field.state().dirty(),\n        &#039;ng-pending&#039;: field =&gt; field.state().pending(),\n      }\n    }),\n  ],\n};<\/code><\/pre>\n<p>The corresponding CSS rules might then look like this, for example:<\/p>\n<pre><code class=\"language-css\">input.ng-valid {\n  border-left: 3px solid darkseagreen;\n}\n\ninput.ng-invalid.ng-dirty {\n  border-left: 3px solid var(--color-error);\n}\n\ninput.ng-pending {\n  border-left: 3px solid var(--color-border);\n}<\/code><\/pre>\n<p>Signal Forms automatically applies the configured classes to the bound input fields:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/validation.png\" alt=\"Input fields in Signal Forms with conditional CSS formatting: green border for valid values, red border for invalid values, and grey border for pending validations\" style=\"max-width: 500px\" \/><\/p>\n<p>If you want to use exactly the same classes as with Reactive or Template-driven Forms, you can use the predefined configuration object <code>NG_STATUS_CLASSES<\/code> from the compat namespace:<\/p>\n<pre><code class=\"language-ts\">import { NG_STATUS_CLASSES } from &#039;@angular\/forms\/signals\/compat&#039;;\n\nprovideSignalFormsConfig({\n  classes: NG_STATUS_CLASSES\n}),<\/code><\/pre>\n<h2>Interop of Signal Forms with Reactive Forms<\/h2>\n<p>Signal Forms integrates seamlessly with existing Reactive Forms. The bridge <code>compatForm<\/code> from <code>@angular\/forms\/signals\/compat<\/code> allows you to connect a Signal Form model with reactive form controls:<\/p>\n<pre><code class=\"language-ts\">import { compatForm, SignalFormControl } from &#039;@angular\/forms\/signals\/compat&#039;;\n\n@Component({ [...] })\nexport class CheckinPage {\n  protected readonly address = new SignalFormControl(\n    this.addressFormModel(),\n    (path) =&gt; {\n      required(path.street);\n      required(path.zipCode);\n      required(path.country);\n    },\n  );\n\n  protected readonly checkinForm = compatForm(this.checkinFormModel, (path) =&gt; {\n    required(path.ticketId);\n  });\n}<\/code><\/pre>\n<p><code>SignalFormControl<\/code> behaves like a regular <code>AbstractControl<\/code> and can be embedded directly into existing Reactive Forms structures. Conversely, Signal Forms also understands legacy form controls, CVA-based (Control Value Accessor) inputs, and classical validators. Signal Forms can therefore also work with existing legacy form controls. The interop bridge to CVA can be disabled per field if needed:<\/p>\n<pre><code class=\"language-html\">&lt;input ngNoCva [field]=&quot;myField&quot;&gt;<\/code><\/pre>\n<h2><code>validateStandardSchema<\/code> with Dynamic Rules<\/h2>\n<p>Standard Schema is a community-driven interface that validation libraries such as <strong>Zod<\/strong> and <strong>Valibot<\/strong> implement. Since Signal Forms ships with a function called <code>validateStandardSchema<\/code> that directly understands this interface, schemas from all these libraries can be used for form validation without having to write a dedicated adapter.<\/p>\n<p>Since Angular 21.2, the schema can also adapt dynamically. Instead of a fixed schema object, you pass <code>validateStandardSchema<\/code> a lambda expression that is internally converted into a <code>computed<\/code>. If a Signal used within it changes, the Computed is automatically re-evaluated and the updated validation rules apply immediately:<\/p>\n<pre><code class=\"language-ts\">import { Signal } from &#039;@angular\/core&#039;;\nimport { SchemaPathTree, validateStandardSchema } from &#039;@angular\/forms\/signals&#039;;\nimport { z } from &#039;zod&#039;;\nimport { Flight } from &#039;.\/flight&#039;;\n\nconst FlightZodSchema = z.object({\n  id: z.number().int(),\n  from: z.string().min(3).max(20),\n  to: z.string().min(3).max(20),\n  date: z.string(),\n  delayed: z.boolean(),\n});\n\nconst StrictFlightZodSchema = z.object({\n  id: z.number().int(),\n  from: z.string().min(10).max(30),\n  to: z.string().min(10).max(30),\n  [...]\n});\n\nexport function validateWithSchema(\n  path: SchemaPathTree&lt;Flight&gt;,\n  strict: Signal&lt;boolean&gt;,\n) {\n  validateStandardSchema(\n    path,\n    () =&gt; strict() ? StrictFlightZodSchema : FlightZodSchema,\n  );\n}<\/code><\/pre>\n<p>This allows, for example, context-dependent validation strategies. The form automatically switches between the lenient and the strict schema as soon as the <code>strict<\/code> signal changes.<\/p>\n<h2><code>disabled<\/code>, <code>readonly<\/code>, and <code>hidden<\/code> with the <code>when<\/code> Property<\/h2>\n<p>Signal Forms knows the helper functions <code>disabled<\/code>, <code>readonly<\/code>, and <code>hidden<\/code> to control input fields depending on the form state. The new feature: the condition is now passed via a <code>when<\/code> property in the parameter object. This makes the API more consistent and makes it possible, in the error case, to also return an explanatory string instead of <code>true<\/code>:<\/p>\n<pre><code class=\"language-ts\">import { disabled, hidden, readonly } from &#039;@angular\/forms\/signals&#039;;\n\ndisabled(path.delay, {\n  when: (ctx) =&gt; (ctx.valueOf(path.delayed) ? false : &#039;not delayed&#039;),\n});\n\nreadonly(path.delay, {\n  when: (ctx) =&gt; ctx.valueOf(path.delayed),\n});\n\nhidden(path.delay, {\n  when: (ctx) =&gt; ctx.valueOf(path.delayed),\n});<\/code><\/pre>\n<p>The <code>ctx<\/code> parameter provides contextual access to other field values, so that complex, cross-field conditions can be expressed cleanly.<\/p>\n<h2>Route Auto Cleanup for Environment Injectors<\/h2>\n<p>In Angular, services can also be configured at the route level, namely via the <code>providers<\/code> property of a route configuration. So far, however, there has been a catch: services registered this way were not cleaned up when leaving the route, but continued to live until the application was closed. The reason lies in the historical behavior of the underlying Environment Injectors. They are the counterpart to those providers that were previously set up at the <code>NgModule<\/code> level, and their original lifecycle behavior was to be preserved.<\/p>\n<p>Since Angular 21.1, this can be changed. With <code>withExperimentalAutoCleanupInjectors<\/code>, the Environment Injectors of a route, including all service instances registered there, are automatically destroyed when leaving the route:<\/p>\n<pre><code class=\"language-ts\">import {\n  provideRouter,\n  withComponentInputBinding,\n  withExperimentalAutoCleanupInjectors,\n} from &#039;@angular\/router&#039;;\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideRouter(\n      routes,\n      withComponentInputBinding(),\n      withExperimentalAutoCleanupInjectors(),\n    ),\n    [...]\n  ],\n};<\/code><\/pre>\n<p>The feature remains experimental for the time being.<\/p>\n<h2>Determining Active Routes with <code>isActive<\/code> as a Signal<\/h2>\n<p>With <code>isActive<\/code>, Angular 22 brings a new way to programmatically determine whether a route is active. The function returns a Signal and is automatically re-evaluated in the template as soon as the route state changes:<\/p>\n<pre><code class=\"language-ts\">import { isActive, Router } from &#039;@angular\/router&#039;;\n\n@Component({ [...] })\nexport class BookingNavigation {\n  private readonly router = inject(Router);\n\n  protected readonly flightSearchActive = isActive(\n    &#039;\/ticketing\/booking\/flight-search&#039;, this.router\n  );\n  protected readonly passengerSearchActive = isActive(\n    &#039;\/ticketing\/booking\/passenger-search&#039;, this.router\n  );\n  protected readonly summaryActive = isActive(\n    &#039;\/ticketing\/booking\/summary&#039;, this.router,\n    { paths: &#039;exact&#039; }\n  );\n}<\/code><\/pre>\n<p>The optional third parameter accepts <code>IsActiveMatchOptions<\/code> and controls how exact the comparison should be:<\/p>\n<ul>\n<li><strong><code>paths<\/code><\/strong> (<code>&#039;exact&#039;<\/code> | <code>&#039;subset&#039;<\/code>): Must all segments match, or is a subset sufficient?<\/li>\n<li><strong><code>matrixParams<\/code><\/strong> (<code>&#039;exact&#039;<\/code> | <code>&#039;subset&#039;<\/code> | <code>&#039;ignored&#039;<\/code>): Comparison of the matrix parameters of the matching segments.<\/li>\n<li><strong><code>queryParams<\/code><\/strong> (<code>&#039;exact&#039;<\/code> | <code>&#039;subset&#039;<\/code> | <code>&#039;ignored&#039;<\/code>): Comparison of the query parameters.<\/li>\n<li><strong><code>fragment<\/code><\/strong> (<code>&#039;exact&#039;<\/code> | <code>&#039;ignored&#039;<\/code>): Comparison of the URL fragment.<\/li>\n<\/ul>\n<p>By default, subset matching applies for <code>paths<\/code>: a route is considered active if it is a subset of the current URL. In the example, <code>paths: &#039;exact&#039;<\/code> was set for <code>summaryActive<\/code>, so that the route must match exactly. In the template, you use the signal directly for the active CSS class:<\/p>\n<pre><code class=\"language-html\">&lt;a [routerLink]=&quot;[&#039;.\/flight-search&#039;]&quot; [class.active]=&quot;flightSearchActive()&quot;&gt;\n  Flight\n&lt;\/a&gt;<\/code><\/pre>\n<hr \/>\n<h2><code>HttpClient<\/code> in Angular 22: <code>FetchBackend<\/code> as the Default<\/h2>\n<p>From Angular 22 onwards, the <code>HttpClient<\/code> uses the <code>FetchBackend<\/code> by default. The explicit <code>withFetch()<\/code> is therefore deprecated and can be removed.<\/p>\n<p>The background: the Fetch API is now available in all relevant browsers and modern JavaScript runtimes. Compared to <code>XMLHttpRequest<\/code> (XHR), it offers a more modern, Promise-based API, better support for streaming scenarios, and is better suited as a shared HTTP abstraction for browsers and SSR. However, Fetch does not support upload progress events.<\/p>\n<p>Accordingly, Angular 22 replaces the previous blanket <code>reportProgress<\/code> option (deprecated) with two dedicated variants that toggle upload and download progress separately:<\/p>\n<pre><code class=\"language-ts\">\/\/ Download progress (works with Fetch)\nhttp.get(&#039;\/large-file&#039;, { reportDownloadProgress: true, observe: &#039;events&#039; });\n\n\/\/ Upload progress (requires withXhr())\nhttp.post(&#039;\/upload&#039;, file, { reportUploadProgress: true, observe: &#039;events&#039; });<\/code><\/pre>\n<p>If <code>reportUploadProgress<\/code> is used together with the <code>FetchBackend<\/code>, Angular throws an exception. This is a deliberately hard hint that <code>withXhr()<\/code> is required in this case.<\/p>\n<p>If you need the original behavior, for example for a progress indicator during upload, switch back to XHR:<\/p>\n<pre><code class=\"language-ts\">provideHttpClient(withXhr());<\/code><\/pre>\n<p>During version upgrades, <code>ng update<\/code> automatically adds <code>withXhr()<\/code>. This prevents unwanted behavior changes in existing applications.<\/p>\n<h2>Template Syntax Improvements in Angular 22<\/h2>\n<p>Angular 21.1, 21.2, and the preview versions of Angular 22 have brought a number of useful extensions for template expressions, which are summarized here. Examples from the respective pull requests serve as illustrations.<\/p>\n<p>Since Angular 21.1, templates support object spread, array spread, and rest arguments in function calls, that is, syntax that was previously only allowed in TypeScript classes:<\/p>\n<pre><code class=\"language-html\">&lt;div [class]=&quot;{ ...baseClasses, active: isActive() }&quot;&gt;&lt;\/div&gt;\n&lt;ul&gt;\n  @for (item of [...preferred, ...rest]; track $index) {\n    &lt;li&gt;{{ item }}&lt;\/li&gt;\n  }\n&lt;\/ul&gt;\n{{ sum(...numbers()) }}<\/code><\/pre>\n<p>Also since 21.1, <code>@switch<\/code> supports multiple consecutive <code>@case<\/code> markers for the same block, analogous to fall-through in other languages:<\/p>\n<pre><code class=\"language-html\">@switch (state) {\n  @case (&#039;a&#039;)\n  @case (&#039;b&#039;) { &lt;p&gt;A or B&lt;\/p&gt; }\n  @case (&#039;c&#039;) { &lt;p&gt;C&lt;\/p&gt; }\n  @default    { &lt;p&gt;Otherwise&lt;\/p&gt; }\n}<\/code><\/pre>\n<p>Angular 21.2 added arrow functions with implicit return values in template expressions. They are especially handy in combination with <code>@for<\/code> and event bindings:<\/p>\n<pre><code class=\"language-html\">@for (item of items(); track item.id) {\n  &lt;button (click)=&quot;select((x) =&gt; x.id === item.id)&quot;&gt;\u2026&lt;\/button&gt;\n}<\/code><\/pre>\n<p>Arrow functions with a block body (<code>{ \u2026 }<\/code>) and pipes within the body are not allowed. Functions that only use their own parameters are hoisted by the compiler to the module level; functions related to the template context are stored on the view to guarantee identity stability.<\/p>\n<p>Also since 21.2, type checks with <code>instanceof<\/code> are possible directly in the template:<\/p>\n<pre><code class=\"language-html\">@if (event instanceof MouseEvent) {\n  &lt;p&gt;ClientX: {{ event.clientX }}&lt;\/p&gt;\n}<\/code><\/pre>\n<p>Exhaustive case distinctions for <code>@switch<\/code> also arrived in 21.2: with <code>@default never;<\/code> at the end of a <code>@switch<\/code> block, TypeScript checks at compile time whether all variants of a union type are covered. If the union is later extended and a new value is not handled, compilation fails:<\/p>\n<pre><code class=\"language-ts\">state: &#039;loggedOut&#039; | &#039;loading&#039; | &#039;loggedIn&#039; = &#039;loggedOut&#039;;<\/code><\/pre>\n<pre><code class=\"language-html\">@switch (state) {\n  @case (&#039;loggedOut&#039;) { &lt;button&gt;Login&lt;\/button&gt; }\n  @case (&#039;loading&#039;)   { &lt;p&gt;Loading ...&lt;\/p&gt; }\n  @case (&#039;loggedIn&#039;)  { &lt;p&gt;Welcome back!&lt;\/p&gt; }\n  @default never;\n}<\/code><\/pre>\n<p>Here, the <code>@switch<\/code> expression (<code>state<\/code>) itself is the union. In the <code>@default<\/code> branch, TypeScript recognizes that, after covering all cases, <code>state<\/code> has the type <code>never<\/code>. If the union is later extended e.g. by <code>&#039;banned&#039;<\/code> without a corresponding <code>@case<\/code>, the compiler complains.<\/p>\n<p>Often, however, you don't switch on the union itself, but on one of its properties, such as the discriminator of a discriminated union. TypeScript can then narrow the queried property, but cannot recognize whether the overarching union is thereby fully covered. Angular 22 closes this gap. With <code>never(&lt;expression&gt;)<\/code>, you can explicitly specify which expression should be checked for full coverage:<\/p>\n<pre><code class=\"language-ts\">state!: { mode: &#039;show&#039;; menu: number } | { mode: &#039;hide&#039; };<\/code><\/pre>\n<pre><code class=\"language-html\">@switch (state.mode) {\n  @case (&#039;show&#039;) { {{ state.menu }} }\n  @case (&#039;hide&#039;) {}\n  @default never(state);\n}<\/code><\/pre>\n<p>The specification <code>never(state)<\/code> explicitly tells the compiler to check full coverage against the overarching <code>state<\/code> union. If it is later extended by another <code>mode<\/code> without a corresponding <code>@case<\/code>, the template compiler reports the error.<\/p>\n<p>Angular 22 brings two further refinements in handling <code>null<\/code> and <code>undefined<\/code>. The behavior of <code>?.<\/code> in templates now corresponds exactly to JavaScript semantics: if the chain breaks at a <code>null<\/code> or <code>undefined<\/code> position, the result is <code>undefined<\/code>. If you need the old Angular-specific behavior, you can wrap the expression with <code>$null(...)<\/code>:<\/p>\n<pre><code class=\"language-html\">{{ user?.profile?.name }}  &lt;!-- now: undefined if the chain breaks --&gt;<\/code><\/pre>\n<p>In addition, the type-check block now allows true TypeScript narrowing via <code>?.<\/code>. After a truthiness check, the type is narrowed as in regular TS code, so that subsequent access without <code>?.<\/code> is type-safe:<\/p>\n<pre><code class=\"language-ts\">@Component({\n  template: `\n    @if (user?.isMember) {\n      {{ user.isMember }}  \n    }\n  `,\n})\nexport class UserComponent {\n  user?: { isMember: boolean };\n}<\/code><\/pre>\n<p>Finally, in Angular 22, <code>\/\/<\/code> and <code>\/* \u2026 *\/<\/code> comments are also allowed within HTML element definitions. This is handy for structuring long attribute lists:<\/p>\n<pre><code class=\"language-html\">&lt;div\n  \/\/ primary button\n  class=&quot;btn btn-primary&quot;\n  \/*\n    Note: only takes effect when `loading` is false\n  *\/\n  [disabled]=&quot;loading()&quot;\n>&lt;\/div&gt;<\/code><\/pre>\n<h2><code>@defer<\/code>: Optional Timeout for <code>on idle<\/code><\/h2>\n<p><code>@defer (on idle)<\/code> can now receive a timeout in milliseconds, analogous to <code>IdleRequestOptions.timeout<\/code>. This prevents a defer block from waiting endlessly for an idle phase that never occurs:<\/p>\n<pre><code class=\"language-html\">@defer (on idle(2000)) {\n  &lt;heavy-widget \/&gt;\n}<\/code><\/pre>\n<h2>More at a Glance<\/h2>\n<ul>\n<li>\n<p><strong><code>httpResource<\/code> and Transfer State:<\/strong> Resources that are preloaded on the server now interact seamlessly with the HTTP Transfer State. This prevents redundant HTTP requests during the first render in the browser. The client simply uses the data already fetched on the server side.<\/p>\n<\/li>\n<li>\n<p><strong>Web MCP Tools (<code>provideExperimentalWebMcpTools<\/code>, <code>declareExperimentalWebMcpTool<\/code>):<\/strong> Angular applications can register AI tools directly in the injector. When the injector is destroyed, the tools are automatically removed again, ideal in combination with route providers and <code>withExperimentalAutoCleanupInjectors<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong><code>ApplicationRef.bootstrap<\/code> with config:<\/strong> <code>bootstrap()<\/code> on the <code>ApplicationRef<\/code> now accepts a configuration object analogous to <code>createComponent<\/code>. This is particularly relevant for micro frontends that are loaded and bootstrapped on demand into specific areas of the page: <code>appRef.bootstrap(MyComponent, { hostElement: document.querySelector(&#039;#root&#039;)! })<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong>Bootstrap under Shadow Roots:<\/strong> Angular can now be started directly under a shadow root. Styles are correctly registered with the parent shadow root in the <code>SharedStylesHost<\/code>. This is another step toward clean Web Component and micro frontend integration.<\/p>\n<\/li>\n<li>\n<p><strong>Wildcard routes with trailing segments (since 21.1):<\/strong> The wildcard segment <code>**<\/code> may now be surrounded by leading and trailing segments, e.g. <code>&#039;foo\/**\/bar&#039;<\/code>. Previously, this was only possible with a custom path matcher. This is particularly useful for shell applications that need to load the appropriate micro frontend based on a pattern.<\/p>\n<\/li>\n<li>\n<p><strong>AI runtime debugging:<\/strong> In dev mode, Angular registers AI debugging tools in the page. Among them is <code>angular:di-graph<\/code>, which provides the complete dependency injection graph (element and environment injectors) for in-page AI assistants. In the future, this will enable AI-assisted analyses of the DI graph directly in the browser.<\/p>\n<\/li>\n<li>\n<p><strong>Trailing-slash location strategies (since 21.2):<\/strong> With <code>TrailingSlashPathLocationStrategy<\/code> and <code>NoTrailingSlashPathLocationStrategy<\/code>, there are two new subclasses that control whether URLs in the address bar should end with or without a trailing <code>\/<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong><code>height<\/code> in <code>ImageLoaderConfig<\/code> (since 21.2):<\/strong> The image loader now accepts a <code>height<\/code> specification in addition to <code>width<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong>Custom transformations for image loaders (since 21.1):<\/strong> The built-in loaders for Cloudflare, Cloudinary, ImageKit, and Imgix accept a <code>transform<\/code> property for provider-specific URL options: <code>provideCloudflareLoader(&#039;https:\/\/cdn.example\/&#039;, { transform: { format: &#039;webp&#039;, sharpen: 50 } })<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong>TypeScript 6 (since 21.2):<\/strong> Angular now supports TypeScript 6. TypeScript 5.9 is no longer supported.<\/p>\n<\/li>\n<li>\n<p><strong>Node.js 26:<\/strong> Angular now officially compiles and runs on Node.js 26.<\/p>\n<\/li>\n<\/ul>\n<h2>Learn More: Angular Architecture Workshop (Remote, Interactive, Advanced)<\/h2>\n<p>Become an expert in enterprise-wide and long-lived Angular applications with our Angular Architecture Workshop!<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/sujet-en.jpg\" alt=\"Angular Architecture Workshop\" style=\"width:600px; max-width:100%; display: block; margin-left: auto; margin-right:auto\" \/><\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/en\/training\/advanced-angular-architecture-workshop\/\">German Version<\/a> | <a href=\"https:\/\/www.angulararchitects.io\/en\/training\/advanced-angular-architecture-workshop\/\">English Version<\/a><\/p>\n<h2>FAQ on Angular 22<\/h2>\n<p><strong>What are the most important new features in Angular 22?<\/strong><br \/>\nThe Resource API (<code>resource<\/code>, <code>rxResource<\/code>, <code>httpResource<\/code>) and Signal Forms are stable. <code>OnPush<\/code> is the new default change detection strategy, and Incremental Hydration is enabled by default. On top of that, there are <code>@Service<\/code>, <code>injectAsync<\/code> with <code>onIdle<\/code> prefetching, the <code>debounced<\/code> function, and numerous extensions to template syntax and the router.<\/p>\n<p><strong>Is Signal Forms ready for production in Angular 22?<\/strong><br \/>\nYes. Signal Forms has left experimental status. Together with the Submission API, dynamic schemas via <code>validateStandardSchema<\/code>, conditional CSS classes, and interop with Reactive Forms, a production-ready, Signal-based form stack is available.<\/p>\n<p><strong>Do I need to adjust my change detection strategy after updating to Angular 22?<\/strong><br \/>\nUsually not. For existing components without an explicit strategy, <code>ng update<\/code> automatically sets <code>ChangeDetectionStrategy.Eager<\/code> to preserve the previous behavior. If you want to actively benefit from <code>OnPush<\/code>, you can migrate gradually, component by component.<\/p>\n<p><strong>Does the <code>@Service<\/code> decorator completely replace <code>@Injectable()<\/code>?<\/strong><br \/>\nNo. <code>@Service()<\/code> is a more ergonomic shorthand for the most common case (<code>providedIn: &#039;root&#039;<\/code>). <code>@Injectable()<\/code> remains available and continues to make sense wherever different provider configurations are used.<\/p>\n<h2>Conclusion<\/h2>\n<p>Angular 22 marks an important point of maturity in the Signal era: with the stable Resource API and Signal Forms, two of the central building blocks for modern, reactive Angular applications are now ready for production use. The new <code>@Service<\/code> decorator, the debouncing function <code>debounced<\/code>, dynamic schemas, and the Submission API round out the picture and show how consistently the Angular team is working on a coherent, ergonomic API design.<\/p>\n<p>The extensions to the template syntax and the innovations in the Router, such as auto cleanup for route injectors and the new reactive <code>isActive<\/code> function, complete a release that is impressive in its breadth. Anyone who has held off on the update over the past few months now gets a stable foundation that allows the move to signal-based development even without experimental features.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With Angular 22, several central Signal APIs leave their experimental status: the Resource API and Signal Forms are now stable and ready for production use. In parallel, OnPush becomes the new default change detection strategy, and the framework gains more ergonomic building blocks for modern, reactive applications with @Service, injectAsync, and debounced. This article highlights [&hellip;]<\/p>\n","protected":false},"author":25,"featured_media":33911,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_price":"","_stock":"","_tribe_ticket_header":"","_tribe_default_ticket_provider":"","_ticket_start_date":"","_ticket_end_date":"","_tribe_ticket_show_description":"","_tribe_ticket_show_not_going":false,"_tribe_ticket_use_global_stock":"","_tribe_ticket_global_stock_level":"","_global_stock_mode":"","_global_stock_cap":"","_tribe_rsvp_for_event":"","_tribe_ticket_going_count":"","_tribe_ticket_not_going_count":"","_tribe_tickets_list":"[]","_tribe_ticket_has_attendee_info_fields":false,"footnotes":""},"categories":[18],"tags":[],"class_list":["post-33917","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Angular 22: The Most Important New Features at a Glance - ANGULARarchitects<\/title>\n<meta name=\"description\" content=\"Angular 22 stabilizes the Resource API and Signal Forms, makes OnPush the default change detection strategy, and brings @Service, injectAsync, debounced, as well as numerous template and router improvements. This article shows the relevant updates using concrete examples.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Angular 22: The Most Important New Features at a Glance - ANGULARarchitects\" \/>\n<meta property=\"og:description\" content=\"Angular 22 stabilizes the Resource API and Signal Forms, makes OnPush the default change detection strategy, and brings @Service, injectAsync, debounced, as well as numerous template and router improvements. This article shows the relevant updates using concrete examples.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-03T19:29:57+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-03T20:49:54+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/en.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"628\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Manfred Steyer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/en.png\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Manfred Steyer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"24 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/\"},\"author\":{\"name\":\"Manfred Steyer\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\"},\"headline\":\"Angular 22: The Most Important New Features at a Glance\",\"datePublished\":\"2026-06-03T19:29:57+00:00\",\"dateModified\":\"2026-06-03T20:49:54+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/\"},\"wordCount\":3346,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg.png\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/\",\"name\":\"Angular 22: The Most Important New Features at a Glance - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg.png\",\"datePublished\":\"2026-06-03T19:29:57+00:00\",\"dateModified\":\"2026-06-03T20:49:54+00:00\",\"description\":\"Angular 22 stabilizes the Resource API and Signal Forms, makes OnPush the default change detection strategy, and brings @Service, injectAsync, debounced, as well as numerous template and router improvements. This article shows the relevant updates using concrete examples.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg.png\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg.png\",\"width\":1672,\"height\":941},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Angular 22: The Most Important New Features at a Glance\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"name\":\"ANGULARarchitects\",\"description\":\"AngularArchitects.io\",\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\",\"name\":\"ANGULARarchitects\",\"alternateName\":\"SOFTWAREarchitects\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"width\":644,\"height\":216,\"caption\":\"ANGULARarchitects\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/github.com\/angular-architects\",\"https:\/\/www.linkedin.com\/company\/angular-architects\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\",\"name\":\"Manfred Steyer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"caption\":\"Manfred Steyer\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Angular 22: The Most Important New Features at a Glance - ANGULARarchitects","description":"Angular 22 stabilizes the Resource API and Signal Forms, makes OnPush the default change detection strategy, and brings @Service, injectAsync, debounced, as well as numerous template and router improvements. This article shows the relevant updates using concrete examples.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/","og_locale":"en_US","og_type":"article","og_title":"Angular 22: The Most Important New Features at a Glance - ANGULARarchitects","og_description":"Angular 22 stabilizes the Resource API and Signal Forms, makes OnPush the default change detection strategy, and brings @Service, injectAsync, debounced, as well as numerous template and router improvements. This article shows the relevant updates using concrete examples.","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/","og_site_name":"ANGULARarchitects","article_published_time":"2026-06-03T19:29:57+00:00","article_modified_time":"2026-06-03T20:49:54+00:00","og_image":[{"width":1200,"height":628,"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/en.png","type":"image\/png"}],"author":"Manfred Steyer","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/en.png","twitter_misc":{"Written by":"Manfred Steyer","Est. reading time":"24 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/"},"author":{"name":"Manfred Steyer","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a"},"headline":"Angular 22: The Most Important New Features at a Glance","datePublished":"2026-06-03T19:29:57+00:00","dateModified":"2026-06-03T20:49:54+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/"},"wordCount":3346,"commentCount":0,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg.png","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/","name":"Angular 22: The Most Important New Features at a Glance - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg.png","datePublished":"2026-06-03T19:29:57+00:00","dateModified":"2026-06-03T20:49:54+00:00","description":"Angular 22 stabilizes the Resource API and Signal Forms, makes OnPush the default change detection strategy, and brings @Service, injectAsync, debounced, as well as numerous template and router improvements. This article shows the relevant updates using concrete examples.","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg.png","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg.png","width":1672,"height":941},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/angular-22-the-most-important-new-features-at-a-glance\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"Angular 22: The Most Important New Features at a Glance"}]},{"@type":"WebSite","@id":"https:\/\/www.angulararchitects.io\/en\/#website","url":"https:\/\/www.angulararchitects.io\/en\/","name":"ANGULARarchitects","description":"AngularArchitects.io","publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.angulararchitects.io\/en\/#organization","name":"ANGULARarchitects","alternateName":"SOFTWAREarchitects","url":"https:\/\/www.angulararchitects.io\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","width":644,"height":216,"caption":"ANGULARarchitects"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/github.com\/angular-architects","https:\/\/www.linkedin.com\/company\/angular-architects\/"]},{"@type":"Person","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a","name":"Manfred Steyer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","caption":"Manfred Steyer"}}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/33917","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=33917"}],"version-history":[{"count":9,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/33917\/revisions"}],"predecessor-version":[{"id":33932,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/33917\/revisions\/33932"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/33911"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=33917"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=33917"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=33917"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}