- Angular
- TypeScript
- 技術 💻
Angular 22新機能まとめ|22.0〜22.2一覧表とng update実測
Angular 22 とは、2026年6月3日にリリースされた Angular のメジャーバージョンで、Signal Forms と resource の安定版化、OnPush の既定化、@Service デコレーターなどが入ったリリースです。 その後 22.1(7月)、22.2(2026年9月23日)で @boundary や Router resources などが追加されています。
この記事では、22.0〜22.2 の新機能を公式ブログ・公式ドキュメント・npm パッケージの型定義で確認して一覧表にまとめ、さらに Angular 21 のプロジェクトを実際に ng update で 22.2.0 に上げたときのメッセージと差分、新機能をコピペで試せるコードと Playwright(Chromium)での実行結果 を載せています。概要だけでなく「上げると何が変わるか」「どう書くか」まで一度に確認できます。
Angular 22 新機能の早見表(22.0〜22.2)
| 分類 | 新機能・変更 | 版 | 状態 |
|---|---|---|---|
| フォーム | Signal Forms(@angular/forms/signals) |
22.0 | 安定版 |
| 非同期 | resource() / httpResource() / rxResource() |
22.0 | 安定版 |
| 非同期 | resource の chain()(別の resource の結果を待つ) |
22.0 | 安定版 |
| 非同期 | debounced()(シグナルのデバウンス) |
22.0 | 実験的 |
| 変更検知 | OnPush が既定、Default は Eager に名称変更 |
22.0 | 既定の変更 |
| DI | @Service()(@Injectable({ providedIn: 'root' }) の簡易版) |
22.0 | 安定版 |
| DI | injectAsync()(サービスの遅延読み込み) |
22.0 | 安定版 |
| テンプレート | スプレッド構文(オブジェクト・配列・関数呼び出し) | 22.0 | 安定版 |
| テンプレート | アロー関数 | 22.0 | 安定版 |
| テンプレート | @switch で複数の @case をまとめる、@default never; |
22.0 | 安定版 |
| テンプレート | 要素の中にコメントを書ける | 22.0 | 安定版 |
| テンプレート | strictTemplates が既定で有効 |
22.0 | 既定の変更 |
| テンプレート | @defer (on idle(500ms)) のタイムアウト指定 |
22.0 | 安定版 |
| HTTP | HttpClient が Fetch API を既定 に(withFetch() は非推奨) |
22.0 | 既定の変更 |
| ルーター | canMatch に第3引数 currentSnapshot、paramsInheritanceStrategy の既定が 'always' |
22.0 | 破壊的変更 |
| SSR | インクリメンタルハイドレーションが既定 | 22.0 | 既定の変更 |
| a11y | Angular Aria(@angular/aria) |
22.0 | 安定版 |
| AI | WebMCP 連携(declareExperimentalWebMcpTool など)、Agent Skills |
22.0 | 実験的 |
| 環境 | TypeScript 6 必須(5.9 は非対応)、Node.js 20 は非対応 | 22.0 | 要件 |
| ビルド | Webpack 系(@angular-devkit/build-angular など)は非推奨 |
22.0 | 非推奨 |
| シグナル | linkedSignal の set オプション |
22.1 | 安定版 |
| DI | @Injectable({ providedIn: 'root' }) → @Service() の移行コマンド |
22.1 | ― |
| HTTP | JSONP(withJsonpSupport())が非推奨 |
22.1 | 非推奨 |
| ビルド | Rolldown によるチャンク最適化が安定版 | 22.1 | 安定版 |
| サポート | メジャー版のサポート期間が 2 年に | 22.1 | 方針 |
| テンプレート | @boundary(エラー境界) |
22.2 | 開発者プレビュー |
| テンプレート | private メンバーをテンプレートから参照可能 | 22.2 | 安定版 |
| ルーター | Router resources(withRouterResources()) |
22.2 | 開発者プレビュー |
| ルーター | withAutoCleanupInjectors() |
22.2 | 安定版 |
| クエリ | viewChild('ref', { read: Injector }) |
22.2 | 安定版 |
| CLI | 型チェックと esbuild のバンドルを並列実行 | 22.2 | 改善 |
| テスト | Vitest 5 に更新 | 22.2 | 改善 |
「安定版」は本番で使ってよい API、「開発者プレビュー」は形が変わる可能性がある API、「実験的」は試用段階の API です。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Angular 22新機能まとめ|22.0〜22.2一覧表とng update実測」
検証環境と前提
| 項目 | バージョン |
|---|---|
| Angular | 21.2.24 → 22.2.0(ng update)、新規作成も 22.2.0 |
| TypeScript | 5.9 系 → 6.0.3 |
| Node.js | 22.22.3 |
| テスト | Vitest(新規 v22 プロジェクトは 5.0.2) |
| ブラウザ確認 | Playwright 1.56.1 + Chromium |
Angular CLI 22.2 は Node.js 22.22.3 以上 / 24.15.0 以上 / 26.0.0 以上が必要です。 22.22.2 で ng new を実行すると、次のメッセージで止まりました。
Node.js version v22.22.2 detected.
The Angular CLI requires a minimum Node.js version of v22.22.3 or v24.15.0 or v26.0.0.
上げる前に node -v を確認 しておくと、スムーズに進められます。
v21 から v22 へ ng update した実測
手順
Angular 21.2.24 で作ったプロジェクトに、変更検知・HTTP・ルーター・サービス・?. を含む小さなコードを入れてから更新しました。
npx ng update @angular/core@22 @angular/cli@22
実際に出たメッセージ
The installed Angular CLI version is outdated.
Installing a temporary Angular CLI versioned 22 to perform the update.
...
Updating package.json with dependency @angular/core to version 22.2.0...
Updating package.json with dependency typescript to version 6.0.3...
** Executing migrations of package '@angular/core' **
❯ Adds the required third argument to canMatch callsites.
Migration completed (No changes made).
❯ Adds `ChangeDetectionStrategy.Eager` to all components.
UPDATE src/app/task-list.ts (520 bytes)
UPDATE src/app/app.ts (352 bytes)
Migration completed (2 files modified).
❯ Adds 'withXhr' to 'provideHttpClient' function calls when the 'HttpXhrBackend' is used.
UPDATE src/app/app.config.ts (398 bytes)
Migration completed (1 file modified).
❯ Adds withNoIncrementalHydration() opt out to provideClientHydration() when incremental hydration is not enabled to retain pre-v22 behavior-.
Migration completed (No changes made).
❯ Migrate broken duplicate outputs.
Migration completed (No changes made).
❯ Wraps optional chaining expressions in $safeNavigationMigration().
Migration completed (No changes made).
❯ Disables the 'nullishCoalescingNotNullable & optionalChainNotNullable extended diagnostics.
UPDATE tsconfig.app.json (624 bytes)
Migration completed (1 file modified).
❯ Adds 'strictTemplates: false' in tsconfig.json when not set.
Migration completed (No changes made).
自動で入った差分
1. 全コンポーネントに ChangeDetectionStrategy.Eager が付く(動きを v21 と同じに保つため)
-import { Component, inject } from '@angular/core';
+import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { TaskService } from './task.service';
@Component({
selector: 'app-task-list',
+ changeDetection: ChangeDetectionStrategy.Eager,
template: `
2. HttpClient に withXhr() が付く(v22 の既定は Fetch API になったため、従来の XHR を維持)
-import { provideHttpClient } from '@angular/common/http';
+import { provideHttpClient, withXhr } from '@angular/common/http';
...
- providers: [provideBrowserGlobalErrorListeners(), provideRouter(routes), provideHttpClient()],
+ providers: [
+ provideBrowserGlobalErrorListeners(),
+ provideRouter(routes),
+ provideHttpClient(withXhr()),
+ ],
3. tsconfig.app.json に拡張診断の抑制が入る
+ "angularCompilerOptions": {
+ "extendedDiagnostics": {
+ "checks": {
+ "nullishCoalescingNotNullable": "suppress",
+ "optionalChainNotNullable": "suppress"
+ }
+ }
+ }
4. package.json:@angular/* が ^22.2.0、typescript が ~6.0.3 に。vitest は ^4.0.8 のまま でした(新規作成した v22 プロジェクトは ^5.0.0)。Vitest 5 にそろえたい場合は別途更新します。
@Injectable({ providedIn: 'root' }) のサービスは ng update では変わりませんでした。@Service() への移行は、22.1 で追加された専用コマンドで行います。
npx ng generate @angular/core:service-migration
-import { Injectable, signal } from '@angular/core';
+import { signal, Service } from '@angular/core';
...
-@Injectable({ providedIn: 'root' })
+@Service()
export class TaskService {
更新後のビルド・テスト
| 項目 | v21.2.24 | v22.2.0(更新後) |
|---|---|---|
ng build(2回目以降、3回) |
4.7 / 8.2 / 7.1 秒 | 4.3 / 5.6 / 2.8 秒 |
ng test --watch=false |
1 passed | 1 passed |
| ビルドエラー | なし | なし |
小さなプロジェクトかつ共有環境での参考値のため、数秒のばらつきがあります。 22.2 の「型チェックとバンドルの並列化」は大きなプロジェクトほど効果が出る改善です(Ninja Squad の解説では大規模プロジェクトで約8秒の改善と紹介されています)。
更新時のチェックリスト
| 確認項目 | 対応 |
|---|---|
| Node.js が 22.22.3 以上 / 24.15.0 以上 | node -v で確認 |
| TypeScript 6 で型エラーがないか | ng build で確認 |
Eager が自動で付いた |
そのままで動作は同じ。新しく書くコンポーネントから OnPush+シグナル にしていく |
withXhr() が付いた |
アップロード進捗が不要なら外して Fetch に移行できる |
ルートの paramsInheritanceStrategy |
既定が 'always' に。従来どおりにするなら 'emptyOnly' を明示(自動移行なし) |
| Webpack ビルダーを使っている | ng update @angular/cli --name use-application-builder で移行を検討 |
| Karma を使っている | ng update @angular/cli --name migrate-karma-to-vitest で Vitest へ |
新規プロジェクト(v22)で変わった点
ng new で作った v22 プロジェクトと ng generate の出力を確認しました。
// ng g c demo/member-card で生成(changeDetection の指定なし=OnPush)
import { Component } from '@angular/core';
@Component({
imports: [],
selector: 'app-member-card',
styleUrl: './member-card.css',
templateUrl: './member-card.html',
})
export class MemberCard {}
// ng g s demo/member で生成(@Service が既定)
import { Service } from '@angular/core';
@Service()
export class Member {}
- コンポーネントは
changeDetectionを書かない=OnPush。 - サービスは
@Service()で生成 されます。 - tsconfig.json に
strictTemplatesの行がありません(既定で有効 になったため)。 - devDependencies は
typescript ~6.0.2、vitest ^5.0.0、jsdom ^30.0.0でした。
新機能をコピペで試す
以下は 1 つのコンポーネントにまとめた動作確認用コードです。Angular 22.2.0 でビルドし、Playwright で操作して結果を確認しました。
// src/app/v22/stock-store.ts
import { Service, signal } from '@angular/core';
export interface StockItem { name: string; qty: number }
@Service()
export class StockStore {
readonly items = signal<StockItem[]>([
{ name: 'ペン', qty: 12 },
{ name: 'ノート', qty: 3 },
]);
}
// src/app/v22/report-exporter.ts(injectAsync で遅延読み込みするサービス)
import { Service, inject } from '@angular/core';
import { StockStore } from './stock-store';
@Service()
export default class ReportExporter {
private readonly store = inject(StockStore); // @Service では inject() を使う
toCsv(): string {
return ['name,qty', ...this.store.items().map((i) => `${i.name},${i.qty}`)].join('\n');
}
}
// src/app/v22/broken-widget.ts(@boundary の確認用に、わざと例外を投げる)
import { Component, input } from '@angular/core';
@Component({
selector: 'app-broken-widget',
template: `<p class="widget">在庫: {{ qtyText() }}</p>`,
})
export class BrokenWidget {
readonly fail = input(false);
protected qtyText() {
if (this.fail()) throw new Error('在庫データを読めませんでした');
return '12個';
}
}
// src/app/v22/v22-page.ts
import { Component, computed, inject, injectAsync, onIdle, resource, signal } from '@angular/core';
import { StockItem, StockStore } from './stock-store';
import { BrokenWidget } from './broken-widget';
type Status = 'draft' | 'pending' | 'processing' | 'done';
function fetchUser(): Promise<{ id: number; teamId: number }> {
return new Promise((r) => setTimeout(() => r({ id: 1, teamId: 7 }), 200));
}
function fetchTeam(teamId: number): Promise<string> {
return new Promise((r) => setTimeout(() => r(`チーム${teamId}`), 200));
}
@Component({
selector: 'app-v22-page',
imports: [BrokenWidget],
template: `
<!-- 1. スプレッド構文 -->
<p id="spread" [class]="{ ...baseClasses, highlight: isHot() }">class spread</p>
<p id="arr-spread">{{ [...baseTags, '新着'].join(' / ') }}</p>
<!-- 2. アロー関数 -->
@for (item of store.items(); track item.name) {
<button
class="use"
// 要素の中にもコメントが書ける(v22)
(click)="store.items.update(list => list.map(i => i.name === item.name ? { ...i, qty: i.qty - 1 } : i))"
>{{ item.name }}: {{ item.qty }}</button>
}
<!-- 3. @switch の複数 @case と @default never -->
@let s = status();
@switch (s) {
@case ('draft') { <p id="sw">下書き</p> }
@case ('pending')
@case ('processing') { <p id="sw">処理中</p> }
@case ('done') { <p id="sw">完了</p> }
@default never;
}
<button id="next" (click)="next()">次の状態</button>
<!-- 4. private メンバーをテンプレートから参照(v22.2) -->
<p id="private">{{ secretLabel }}</p>
<!-- 5. resource の chain() -->
<p id="chain">user={{ user.status() }} team={{ team.status() }} {{ team.hasValue() ? team.value() : '' }}</p>
<!-- 6. injectAsync -->
<button id="export" (click)="exportCsv()">CSV出力</button>
<pre id="csv">{{ csv() }}</pre>
<!-- 7. @boundary(v22.2 developer preview) -->
<button id="break" (click)="fail.set(true)">ウィジェットを失敗させる</button>
@boundary {
<app-broken-widget [fail]="fail()" />
} @error {
<p id="fallback">表示できませんでした: {{ $error.message }}
<button id="retry" (click)="fail.set(false); $reset()">再試行</button></p>
}
`,
})
export class V22Page {
protected readonly store = inject(StockStore);
protected readonly baseClasses = { card: true, rounded: true };
protected readonly baseTags = ['文具', '備品'];
protected readonly isHot = computed(() => this.store.items().some((i: StockItem) => i.qty < 3));
private readonly order: Status[] = ['draft', 'pending', 'processing', 'done'];
protected readonly status = signal<Status>('draft');
next() {
const i = this.order.indexOf(this.status());
this.status.set(this.order[(i + 1) % this.order.length]);
}
private readonly secretLabel = 'private でも表示できます';
protected readonly user = resource({ loader: () => fetchUser() });
protected readonly team = resource({
params: ({ chain }) => {
const u = chain(this.user); // user が resolved になるまで team は loading のまま
return u ? { teamId: u.teamId } : undefined;
},
loader: ({ params }) => fetchTeam(params.teamId),
});
private readonly exporter = injectAsync(() => import('./report-exporter'), { prefetch: onIdle });
protected readonly csv = signal('');
async exportCsv() {
const svc = await this.exporter();
this.csv.set(svc.toCsv());
}
protected readonly fail = signal(false);
}
実行結果(Playwright / Chromium)
| 機能 | 操作 | 画面の結果 |
|---|---|---|
| オブジェクトのスプレッド | 初期表示 | class="card rounded" |
| 配列のスプレッド | 初期表示 | 文具 / 備品 / 新着 |
| アロー関数 | 「ノート: 3」をクリック | ノート: 2、在庫が3未満になり class に highlight が追加 |
| @switch 複数 @case | 「次の状態」を順に押す | 下書き → 処理中 → 処理中 → 完了(pending と processing が同じ表示) |
| private 参照 | 初期表示 | private でも表示できます |
| resource の chain() | 読み込み中 | user=resolved team=loading |
| 〃 | 約0.25秒後 | user=resolved team=resolved チーム7 |
| injectAsync | 「CSV出力」 | name,qty / ペン,12 / ノート,2 |
| @boundary | 「ウィジェットを失敗させる」 | 表示できませんでした: 在庫データを読めませんでした(子は非表示) |
@boundary の $reset() |
「再試行」 | 在庫: 12個 に戻る |
injectAsync で読み込むサービスは別チャンクになりました。 ng build の出力に次の行があります。
Lazy chunk files | Names | Raw size | Estimated transfer size
chunk-BHLxGM3m.js | report-exporter | 310 bytes | 310 bytes
@boundary で捕まえたエラーも、コンソールには ERROR Error: 在庫データを読めませんでした として記録されました。画面は代替表示に切り替わり、ログは残る ので、原因調査もしやすい動きです。
つまずいた点と直し方
1. @default never は、シグナル呼び出しを直接渡すと型が絞り込まれない
@switch (status()) と書いたところ、すべての @case を書いていても次のエラーになりました。
TS2322: Type 'Status' is not assignable to type 'never'.
Type '"draft"' is not assignable to type 'never'.
@let s = status(); で一度変数に入れ、@switch (s) にすると通ります。 その状態で @case ('done') を消すと、期待どおり抜けを検出しました。
TS2322: Type '"done"' is not assignable to type 'never'.
2. chain() の戻り値は undefined を含む
chain(this.user).teamId と書くと TS2532: Object is possibly 'undefined'. になりました。公式の例と同じく u ? { ... } : undefined で分岐 させると通ります。
主要な新機能の解説
OnPush 既定化と Eager
v22 では changeDetection を書かないコンポーネントは OnPush になります。 OnPush は「入力が変わった・イベントが起きた・読んでいるシグナルが変わった」ときだけ再描画する方式です。シグナルで状態を持っていれば、書き方を変える必要はほとんどありません。既存コードは ng update で Eager(旧 Default)が自動で付くので、動きは変わりません(OnPush の実測は「Angular Signals使い方」の記事で紹介しています)。
@Service と injectAsync
| 項目 | @Injectable({ providedIn: 'root' }) |
@Service() |
|---|---|---|
| 役割 | アプリ全体で1つのサービス | 同じ(簡潔に書ける) |
| 依存の受け取り | コンストラクタ引数 / inject() |
inject() のみ |
| ルートに自動登録しない | @Injectable() |
@Service({ autoProvided: false }) |
ng g s の既定 |
v21 まで | v22 から |
injectAsync は「使うときに初めて読み込む」サービス を作ります。レポート出力や重いライブラリを使う処理など、最初の画面表示に不要なサービスに向いています。prefetch: onIdle を付けるとブラウザが空いたときに先読みします。
Signal Forms の安定版化
form()・FormField([formField])・FormRoot([formRoot])・required / email / validateAsync などが v22.0 で安定版 になりました。v22 では minDate() / maxDate()、getError()、reloadValidation()、debounce(field, 'blur')、全ルールでの when オプションなどが追加されています。ログイン・会員登録フォームの完成コードは「Angular Signal Forms」の記事で紹介しています。
HttpClient の Fetch 既定化
v22 では provideHttpClient() だけで Fetch API が使われます。 withFetch() は不要になり非推奨です。Fetch ではアップロード進捗が取れないため、進捗が必要な場合は withXhr() を指定します。reportProgress は非推奨になり、reportUploadProgress / reportDownloadProgress が追加されました。
Router resources(22.2、開発者プレビュー)
ルート定義でデータ取得を宣言し、複数ルートのデータを並列に読み込む仕組みです。公式の解説に沿ったコードを示します(この記事では未実行 です)。
provideRouter(routes, withComponentInputBinding(), withRouterResources());
// ルート定義
{
path: 'races',
component: Races,
resources: (context) => {
const page = computed(() => context.queryParams()['page']);
const races = httpResource<Array<RaceModel>>(() => `/api/races?page=${page()}`);
return { races };
},
}
nonBlocking() で囲むと、読み込みを待たずに画面遷移します。ガードや resource の中から throw new RedirectCommand(...) でリダイレクトできるようにもなりました。
よくある質問
Q. Angular 22 に上げると既存の画面の動きは変わりますか?
A. ng update を使えば、変更検知(Eager の付与)や HttpClient(withXhr の付与)は従来どおりの動きに保たれます。 ただし paramsInheritanceStrategy の既定変更は自動移行されないため、子ルートでパラメーターを読んでいる箇所は確認しておくと安心です。
Q. Node.js と TypeScript はどのバージョンが必要ですか? A. TypeScript 6 が必須 です。Node.js は 20 が非対応になり、Angular CLI 22.2 では 22.22.3 以上 / 24.15.0 以上 / 26.0.0 以上 が必要でした(22.22.2 では起動時に停止)。
Q. @boundary は本番で使えますか? A. 22.2 時点では開発者プレビュー です。API が変わる可能性があるため、まずは影響範囲の小さいウィジェットから試すのがおすすめです。
Q. Webpack を使っているプロジェクトはどうなりますか?
A. v22 では Webpack 系のビルダーは非推奨 ですが、まだ動作します。esbuild ベースの @angular/build:application へは ng update @angular/cli --name use-application-builder で移行できます。
まとめ
- Angular 22 は Signal Forms・resource が安定版になり、OnPush が既定 になったリリースです。
- 22.1 で linkedSignal の set・@Service 移行コマンド、22.2 で @boundary・private 参照・Router resources が加わりました。
ng updateは Eager・withXhr・拡張診断の抑制を自動で入れ、動きを保ったまま 上げられます。- @Service への置き換えは
ng g @angular/core:service-migrationで別途実行します。 - テンプレートで スプレッド構文・アロー関数・複数 @case・@default never が使え、コードが短くなります。
- Node.js 22.22.3 以上と TypeScript 6 を先に用意しておくと、更新がスムーズです。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Angular 22新機能まとめ|22.0〜22.2一覧表とng update実測」
株式会社RJCでは、フレームワークの新しいバージョンを実際のプロジェクトで試し、更新の手順や気づきをメンバー同士で共有しています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。