Angular 22 : Signals, Zoneless & Everything That Finally Shipped
All articles
AngularTypeScriptFrontendWeb Development

Angular 22 : Signals, Zoneless & Everything That Finally Shipped

June 14, 20266 min read

Angular has always been the framework that enterprises trust when "good enough" isn't enough. Version 22 doesn't reinvent the wheel — it finishes what the team started with Angular 17's signals revolution and delivers a surprisingly complete picture of what modern Angular development looks like in 2026.

Here's what's worth your attention.


Zoneless Change Detection Is Finally Production-Ready

If you've been watching Angular's roadmap for the past two years, you knew this was coming. Zone.js — the invisible monkey-patching layer that made Angular's change detection work for years — is now fully optional in Angular 22, and the team has declared the zoneless path stable.

What does this mean in practice? Smaller bundles, faster initial renders, and a dramatically simpler mental model. Your components only update when signals they read actually change.

To opt in at the application level:

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideZonelessChangeDetection(),
  ],
});

The Experimental prefix is gone in Angular 22. It's just provideZonelessChangeDetection() now.


Signal Inputs, Outputs and Queries — The New Default

Angular 22 completes the signal-based component API. Inputs, outputs, view queries, and content queries all have signal equivalents that are now the recommended way to write components.

Signal Inputs

import { Component, input, computed } from '@angular/core';

@Component({
  selector: 'app-user-card',
  template: `
    <div class="card">
      <h2>{{ fullName() }}</h2>
      <p>{{ role() }}</p>
    </div>
  `,
})
export class UserCardComponent {
  firstName = input.required<string>();
  lastName  = input.required<string>();
  role      = input<string>('Developer');

  fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
}

Notice there's no ngOnChanges, no @Input() decorator, and the value is a signal — so fullName recomputes automatically whenever firstName or lastName changes.

Signal Outputs

import { Component, output } from '@angular/core';

@Component({
  selector: 'app-search-bar',
  template: `<input (input)="onType($event)" placeholder="Search..." />`,
})
export class SearchBarComponent {
  searched = output<string>();

  onType(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    this.searched.emit(value);
  }
}

Signal ViewChild

import { Component, viewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-canvas',
  template: `<canvas #myCanvas></canvas>`,
})
export class CanvasComponent implements AfterViewInit {
  canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('myCanvas');

  ngAfterViewInit() {
    const ctx = this.canvas().nativeElement.getContext('2d');
    // draw something...
  }
}

The Resource API: Async Data Meets Signals

One of the most anticipated additions that lands stable in Angular 22 is the Resource API — a first-class way to load async data tied directly to signals.

import { Component, signal, resource } from '@angular/core';

interface Post {
  id: number;
  title: string;
  body: string;
}

@Component({
  selector: 'app-post-viewer',
  template: `
    @if (post.isLoading()) {
      <p>Loading...</p>
    } @else if (post.error()) {
      <p class="error">Failed to load post.</p>
    } @else {
      <article>
        <h1>{{ post.value()?.title }}</h1>
        <p>{{ post.value()?.body }}</p>
      </article>
    }

    <div class="nav">
      <button (click)="prev()">← Previous</button>
      <button (click)="next()">Next →</button>
    </div>
  `,
})
export class PostViewerComponent {
  postId = signal(1);

  post = resource({
    request: () => this.postId(),
    loader: ({ request: id }) =>
      fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
        .then(r => r.json() as Promise<Post>),
  });

  prev() { this.postId.update(id => Math.max(1, id - 1)); }
  next() { this.postId.update(id => id + 1); }
}

When postId changes, the resource automatically re-fetches. Loading and error states are signals too — no RxJS required for the common case.


@defer Gets Smarter Prefetching

Angular's @defer block (introduced in v17) has been quietly getting more powerful. Angular 22 adds prefetch strategies that let you hint the browser to load a deferred chunk before the user actually triggers it.

<!-- Load the heavy chart component when the user is idle,
     but start prefetching as soon as the trigger is visible -->
@defer (on interaction; prefetch on idle) {
  <app-analytics-chart [data]="chartData()" />
} @placeholder {
  <div class="chart-skeleton"></div>
} @loading (minimum 300ms) {
  <app-spinner />
} @error {
  <p>Chart unavailable.</p>
}

The prefetch on idle hint tells Angular to request the JS chunk during browser idle time, so by the time the user clicks, the module is already in cache. No extra configuration — it just works.


Signal-Based Reactive Forms (Preview)

The classic ReactiveFormsModule is showing its age in a signal-first world. Angular 22 ships a preview of signal-based forms that feel considerably more ergonomic.

import { Component } from '@angular/core';
import { signalForm, Validators } from '@angular/forms/signal'; // preview API

@Component({
  selector: 'app-login',
  template: `
    <form (ngSubmit)="submit()">
      <input [formField]="form.email" type="email" />
      @if (form.email.errors()?.required) {
        <span>Email is required</span>
      }

      <input [formField]="form.password" type="password" />

      <button type="submit" [disabled]="!form.valid()">Log in</button>
    </form>
  `,
})
export class LoginComponent {
  form = signalForm({
    email:    ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]],
  });

  submit() {
    if (this.form.valid()) {
      console.log(this.form.value());
    }
  }
}

Validity, dirty state, and individual field errors are all signals — which means they compose naturally with computed() and integrate cleanly with the rest of your reactive logic.


Improved SSR: Partial Hydration Is Stable

Angular 22 stabilizes partial hydration — the ability to ship a fully server-rendered page and hydrate only the components that actually need interactivity, leaving static regions as plain HTML.

// app.config.ts
import { provideClientHydration, withPartialHydration } from '@angular/platform-browser';

export const appConfig: ApplicationConfig = {
  providers: [
    provideClientHydration(withPartialHydration()),
  ],
};

Then, per-component:

@Component({
  selector: 'app-static-footer',
  template: `...`,
  // This component is never hydrated — stays as raw HTML in the browser
  hydration: { mode: 'never' },
})
export class StaticFooterComponent {}

The result is noticeably faster Time to Interactive on content-heavy pages, with zero JavaScript downloaded for components that don't need it.


A Cleaner CLI: ng generate Gets Signal-First Scaffolding

Run ng generate component today and you get a class with @Input() decorators and ngOnInit. In Angular 22, the generated output defaults to the signal-first style:

ng generate component user-profile
// Generated output (Angular 22 default)
import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  imports: [],
  template: `<p>user-profile works!</p>`,
})
export class UserProfileComponent {
  // Define inputs with input() and outputs with output()
}

You can revert to the classic style with --no-signals for legacy projects, but the new default reflects where the ecosystem is heading.


Should You Upgrade?

If you're on Angular 19 or 20, the migration is smooth — the Angular team has kept their promise of zero breaking changes in the public API. Run:

ng update @angular/core@22 @angular/cli@22

The update schematics handle the mechanical parts automatically. For larger codebases, adopt zoneless and signal components incrementally — both work alongside the classic model until you're ready to fully commit.

Angular 22 doesn't feel like a revolutionary release because the revolution happened gradually over the last four versions. What it does feel like is a framework that finally knows exactly what it wants to be — and executes on it without apology.