Free Webinar: How to

Implement and Control AI

in Your Dev Team.

Chapters

    7 Features of Angular 22 Every Dev Needs to Know

    With the release of Angular 22 at the beginning of June 2026, we got several major and minor changes worth noting. This release focuses on making everyday development faster and easier, from closing gaps in template syntax to simplifying the migration path to signal-based forms.

    With that in mind, we know most people don’t have time to read entire changelogs to catch up, so we put together a guide to what we think are the 7 most important features added in version 22 that make the framework more consistent and easier to work with.


    Key Takeaways:

    • Templates now support the spread operator, arrow functions, and the instanceof keyword, which reduce the gap between what’s valid in TypeScript and what you can write directly in Angular templates.
    • Multiple switch case matching is now supported, allowing several cases to share the same block of template logic without duplicating code.
    • The new standalone isActive() helper returns a signal instead of a boolean, fitting naturally into the signals-based reactivity model.
    • provideStabilityDebugging() logs pending tasks and stack traces when an app fails to stabilize, so developers can see exactly what’s keeping it unstable.
    • Resource Snapshots make it possible to transform, compose, and convert resources using standard signal tools, bringing them closer to the rest of the signals ecosystem.
    • SignalFormControl acts as a bridge between Signal Forms and Reactive Forms, letting teams introduce signal-based behavior step by step without rewriting existing form structures.
    • The new @Service decorator is a simpler, cleaner alternative to @Injectable for the most common case: a singleton service built around inject().

    Free ebook: The Ultimate Guide to Angular Evolution. Discover all changes from version 14 to 22 and read expert insights on AI.

    Feature 1. New expressions supported in templates

    Developers commonly use expressions in TypeScript that Angular templates don’t support, creating a noticeable gap between what’s valid in regular application code and what you can write directly in a template.

    Angular 22 closes part of that gap with support for three commonly used expressions: the spread operator (for both array elements and object properties), arrow functions, and the instanceof keyword.

    @Component({
      selector: 'template-expressions',
      template: `
        <!-- Spread operator -->
        @let numbers = [1, 2, 3];
        @let moreNumbers = [...numbers, 4, 5, 6];
    
        @let user = { name: 'John', email: 'john@example.com' };
        @let adminUser = { ...user, role: 'admin' };
    
        <button (click)="log(...moreNumbers)">Log</button>
    
        <!-- Arrow functions -->
        <button (click)="counter.update((value) => value - 1)">-1</button>
        <span>{{ counter() }}</span>
        <button (click)="counter.update((value) => value + 1)">+1</button>
    
        <!-- instanceof keyword -->
        @for (pet of pets; track $index) {
          @if (pet instanceof Dog) {
            <p>This dog name is {{ pet.name }}</p>
          }
    
          @if (pet instanceof Cat) {
            <p>This cat name is {{ pet.name }}</p>
          }
        }
      `,
    })
    export class TemplateExpressions {
      readonly counter = signal(0);
    
      readonly Dog = Dog;
      readonly Cat = Cat;
      readonly pets: Pet[] = [new Dog('Garry'), new Cat('Larry')];
    
      log(...items: unknown[]): void {
        items.forEach((item) => {
          console.log(item);
        });
      }
    }

    This doesn’t close the gap fully, but it narrows it and makes templates easier to write and read, cuts down on workarounds, and the Angular team plans to support more expressions over time.

    Feature 2. Multiple switch case matching

    Before version 22, Angular had no straightforward way to share template logic across multiple switch cases. Developers had to either duplicate code across cases or rely on workarounds, which made templates more verbose and harder to maintain.

    Now the compiler recognizes empty cases that fall through to the next one, so multiple case values can share the same block of template logic, similar to how JavaScript switch statements work.

    @Component({
      selector: 'switch-case-matching',
      template: `
        @switch (status()) {
          @case ('pending')
          @case ('processing') {
            <loading-widget />
          }
          @case ('completed') {
            <success-widget />
          }
        }
      `,
      imports: [LoadingWidget, SuccessWidget],
    })
    export class SwitchCaseMatching {
      status = signal<Status>('pending');
    }

    This makes templates cleaner, reduces duplication, and makes the control flow more intuitive since it now resembles standard JavaScript more closely.


    📖 Read also: TOP 5 Features of Angular 22 for Better Performance


    Feature 3. Standalone isActive function

    Checking whether a route is active used to rely on Router.isActive(), which just returns a boolean. That works fine, but it doesn’t fit the framework’s signal-based reactivity model, and it keeps the functionality tied to the router service even where a smaller, tree-shakable standalone API would work better.

    The new standalone isActive() helper returns a signal that tells you whether a given URL or UrlTree is currently active. Unlike Router.isActive(), it’s reactive and updates automatically as router state changes, tracking successful navigation internally. Router.isActive() is now deprecated as a result.

    @Component({ … })
    export class IsActiveDemo {
      private readonly _router = inject(Router);
    
    
      readonly isUsersActive = isActive('/users', this._router);
    
    
      readonly isBestsellerProductsActive = isActive(
      this._router.createUrlTree(['/products'], { queryParams: { bestseller: true } }),
        this._router,
        {
          paths: 'exact',
          queryParams: 'subset',
        },
      );
    }

    isActive() fits naturally into signal-driven components and stays in sync with navigation state without manual checks. As a standalone API, it also improves tree-shaking and supports smaller bundles in apps that don’t rely on the router-based method.

    Feature 4. Stability debugging

    When an Angular app takes longer than expected to stabilize, especially with hydration, zoneless setups, or complex change detection, pinpointing the cause has typically meant a lot of trial and error.

    provideStabilityDebugging() helps by showing why an app is still unstable after the expected 9-second limit. It’s automatically enabled when you use provideClientHydration() in development mode, but you can also add it manually when debugging production issues. In apps using Zone.js, it can work with the task-tracking plugin to give more detailed information about macrotasks and where they were created.

    import { ApplicationConfig, provideStabilityDebugging } from '@angular/core';
    import 'zone.js/plugins/task-tracking'; // import it if you're using Zone.js
    
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideStabilityDebugging()
      ]
    };

    When the app fails to stabilize in time, the utility logs pending tasks to the console along with their stack traces:

    ---- Application did not stabilize within 9 seconds ----
    
    PendingTasks keeping application unstable:
      Error: Task stack tracking error
        at HttpClient.get (http-service.ts:23)
        at DataService.loadData (data.service.ts:15)

    This makes it much easier to investigate stabilization issues, whether you’re debugging hydration problems, diagnosing zoneless behavior, or tracking down hard-to-find async work, since you can see exactly what’s pending and the code behind it instead of guessing.

    Feature 5. Resource snapshot

    Angular resources are useful for managing async state, but they’ve been notoriously hard to compose. Unlike signals, which combine easily with tools like computed and linkedSignal, resources are harder to transform or derive from one another, which has made patterns like mapping one resource into another, or building reusable composition helpers, more complicated.

    Resource Snapshots address that by giving a resource an explicit representation of its current state: a snapshot signal describing its status along with its value or error. Angular also introduced the resourceFromSnapshot() method, which converts a reactive snapshot back into a resource, bridging resources and signals and allowing higher-level resource composition on top of existing signal APIs. In practice, a resource can now be transformed, combined, or adapted more naturally: you can work with its snapshot form, compose it using standard signal tools, and convert it back into a resource when needed.

    function withPreviousValue<T>(input: Resource<T>): Resource<T> {
      const derived = linkedSignal<ResourceSnapshot<T>, ResourceSnapshot<T>>({
        source: input.snapshot,
        computation: (snap, previous) => {
        if (
          snap.status === 'loading' && 
          previous && 
          previous.value.status !== 'error'
        ) {
            // When the input resource enters loading state, we keep the value
            // from its previous state, if any.
            return {status: 'loading' as const, value: previous.value.value};
          }
          // Otherwise we simply forward the state of the input resource.
          return snap;
        },
      });
      return resourceFromSnapshots(derived);
    }

    This opens the door to richer composition: reusable utilities for loading, error, and value handling; mapping one resource into another; and modeling behaviors that were previously hard or impossible to express clearly. Resource Snapshots bring resources closer to Angular’s wider reactive model by integrating them more naturally with the rest of the signals ecosystem.

    Feature 6. SignalFormControl

    Signal Forms, introduced in Angular 21, offer powerful capabilities, but most teams work with existing applications built around Reactive Forms, and rewriting an entire form just to adopt the new model rarely makes sense. What those teams need instead is a way to gradually introduce signal-based behavior without giving up the surrounding FormGroup structure and existing forms infrastructure.

    SignalFormControl acts as that bridge between Signal Forms and Reactive Forms. It lets you use a signal-based control within a standard FormGroup or FormArray, passing value, status, and validity information through the existing form hierarchy. Angular’s migration guide presents it as a step-by-step path for moving leaf controls to signals while keeping the parent form structure in place. That means signal-based rules, like declarative validation, debouncing, and async validation, can now be used in forms that are otherwise still built with Reactive Forms, so teams can adopt newer form capabilities where they matter most without converting everything at once.

    @Component({
      selector: 'app-user-form',
      imports: [ReactiveFormsModule],
      template: `
        <form [formGroup]="userForm">
          <input formControlName="firstName" />
          <input formControlName="lastName" />
          <input formControlName="email" />
        </form>
      `,
    })
    export class UserFormComponent {
      private readonly _fb = inject(FormBuilder);
    
      readonly emailControl = new SignalFormControl<string>('', (path) => {
        required(path, { message: 'Email is required' });
        email(path, {message: 'Provide a valid email address'});
        validateHttp(path, {
          request: ({ value }) => `/api/check-username?username=${value()}`,
          onSuccess: (response: { taken: boolean }) => {
            if (response.taken) {
              return {
                kind: 'usernameTaken',
                message: 'Username is already taken',
              };
            }
            return null;
          },
          onError: () => ({
            kind: 'networkError',
            message: 'Could not verify username availability',
          }),
        });
      });
    
      readonly userForm = this._fb.nonNullable.group({
        firstName: ['', Validators.required],
        lastName: ['', Validators.required],
        email: this.emailControl,
      });
    }
    

    This makes migrating existing applications to Signal Forms much more realistic. Teams can introduce signal-based validation and control logic in stages, keep their current Reactive Forms architecture, and update forms step by step instead of doing a major rewrite, reducing the risk of adoption and creating a smoother path toward Angular’s new signal-based forms model.


    ⭐Check out: Signal Forms Certification with a Google Developer Expert


    Feature 7. @Service decorator

    Angular services are commonly defined with @Injectable, and Angular’s documentation still presents @Injectable({ providedIn: ‘root’ }) as the standard way to create an application-wide singleton service. That approach is flexible, but it also takes more steps than many teams need, and in modern Angular applications that lean heavily on inject(), the old pattern can feel overly complicated for something as simple as declaring a root-level service.

    The new @Service decorator is an alternative to @Injectable made specifically for that common singleton-service case. It’s root-provided by default, can opt out of automatic provision, favors inject() over constructor-based injection, and trims the API surface down to a simpler factory-based model instead of the broader @Injectable configuration shape.

    @Service()
    export class PostService {
      private readonly _httpClient = inject(HttpClient);
      private readonly _authService = inject(AuthService);
    
    
      getUserPosts(): Observable<Post[]> {
        return this._httpClient.get<Post[]>('/api/posts/' + this._authService.userId);
      }
    }

    The main advantage is clarity: @Service better reflects how many Angular services are written today, singleton by default, lightweight, and built around functional dependency injection, while @Injectable remains available for more advanced dependency injection scenarios.

    Free ebook: The Ultimate Guide to Angular Evolution. Discover all changes from version 14 to 22 and read expert insights on AI.

    Summary

    Angular 22 isn’t built around one headline feature. It’s mostly about smoothing edges developers have been running into for a while: template syntax gets closer to plain TypeScript, resources finally work with the rest of the signals ecosystem, and the migration paths for forms and dependency injection let you adopt new patterns instead of ripping out what already works.

    None of these seven changes is dramatic on its own. But together they show fewer workarounds and tighter signals integration. If you’ve been holding off on updating, we believe this release is worth the time.

    Every Angular change you missed, covered in one place

    Our ebook, “The Ultimate Guide to Angular Evolution,” covers every change from Angular 14 to 22, so you don’t have to dig through changelogs.

    One handbook, always within reach.

    ebook "The Ultimate Guide to Angular Evolution" 2026 edition with Angular 22
    Written by
    Miłosz
    Passionate about web application development, enthusiast for clean code and best practices. Focused on becoming better at Angular every day. In his free time, he enjoys cooking and listening to podcasts.

    Social Media

     
    The latest articles