- Angular
Angularバージョンアップ手順|ng updateとエラー対処一覧
Angularのバージョンアップとは、ng update コマンドで package.json の依存を新しい版に書き換え、同時に移行用のプログラム(migration)でソースコードを自動修正する作業です。 メジャーバージョンは1つずつ上げるのが決まりで、v20 から v22 へ進むなら「v20 → v21 → v22」と2回に分けます。
この記事では、Angular 20 のプロジェクトを v21、v22 へ順に上げ、そのときの ng update の出力・途中で出たエラー文・自動で書き換わった差分 をそのまま載せています。あわせて、Angular と Node.js・TypeScript・RxJS の対応表、エラーと対処の一覧、standalone や制御フローなどの移行 schematic をコマンドつきの早見表にまとめました。
確認環境:Ubuntu 24.04 / Node.js 22.22.2 と 24.21.0 / npm 10.9.7 / Angular CLI 20.3.37 → 21.2.24 → 22.2.1 / TypeScript 5.9.3 → 6.0.3 / Vitest 5.0.3。出力中のフォルダは /home/taro/shop に置き換えています。
Angularバージョンアップの早見表
| 順番 | やること | コマンド |
|---|---|---|
| 1 | 今の版を確かめる | ng version |
| 2 | 作業前の変更をコミットしておく | git status → git commit |
| 3 | 上げられる版を一覧で見る | ng update |
| 4 | 次のメジャーへ上げる(1つずつ) | ng update @angular/core@21 @angular/cli@21 |
| 5 | ビルドとテストで確かめてコミット | ng build → ng test → git commit |
| 6 | さらに次のメジャーへ | ng update @angular/core@22 @angular/cli@22 |
| 7 | 任意の移行 schematic を1つずつ流す | ng generate @angular/core:inject など |
| 8 | 最後に確認する | ng update(「everything seems to be in order」と出れば完了) |
ポイントは3つです。「メジャーは1つずつ」「上げる前にコミット」「上げたらビルドとテスト」。 この3つを守ると、どの段階で何が変わったかを Git の履歴で追えるようになります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Angularバージョンアップ手順|ng updateとエラー対処一覧」
AngularとNode.js・TypeScript・RxJSの対応表
ng update を始める前に、上げたい先の Angular が、今の Node.js で動くか を確かめます。次の表は Angular 公式ドキュメントの「Version compatibility」のページを開いて写したものです(^22.12.0 は「22.12.0 以上で、22 系の範囲」という意味です)。
サポート中のバージョン
| Angular | Node.js | TypeScript | RxJS |
|---|---|---|---|
| 22.0.x | ^22.22.3 / ^24.15.0 / ^26.0.0 | 6.0.0 以上 6.1.0 未満 | ^6.5.3 / ^7.4.0 |
| 21.0.x〜21.2.x | ^20.19.0 / ^22.12.0 / ^24.0.0 | 5.9.0 以上 6.0.0 未満 | ^6.5.3 / ^7.4.0 |
| 20.2.x・20.3.x | ^20.19.0 / ^22.12.0 / ^24.0.0 | 5.8.0 以上 6.0.0 未満 | ^6.5.3 / ^7.4.0 |
| 20.0.x・20.1.x | ^20.19.0 / ^22.12.0 / ^24.0.0 | 5.8.0 以上 5.9.0 未満 | ^6.5.3 / ^7.4.0 |
それより前のバージョン
| Angular | Node.js | TypeScript | RxJS |
|---|---|---|---|
| 19.0.x〜19.2.x | ^18.19.1 / ^20.11.1 / ^22.0.0 | 5.5.0 以上(上限は 19.0 が 5.7.0 未満、19.1 が 5.8.0 未満、19.2 が 5.9.0 未満) | ^6.5.3 / ^7.4.0 |
| 18.0.x〜18.2.x | ^18.19.1 / ^20.11.1 / ^22.0.0 | 5.4.0 以上(18.0 は 5.5.0 未満、18.1 以降は 5.6.0 未満) | ^6.5.3 / ^7.4.0 |
| 17.0.x〜17.3.x | ^18.13.0 / ^20.9.0 | 5.2.0 以上(17.0 は 5.3.0 未満、17.1・17.2 は 5.4.0 未満、17.3 は 5.5.0 未満) | ^6.5.3 / ^7.4.0 |
| 16.0.x〜16.2.x | ^16.14.0 / ^18.10.0 | 4.9.3 以上(16.0 は 5.1.0 未満、16.1 以降は 5.2.0 未満) | ^6.5.3 / ^7.4.0 |
| 15.0.x〜15.2.x | ^14.20.0 / ^16.13.0 / ^18.10.0 | 15.0 は ~4.8.2、15.1 以降は 4.8.2 以上 5.0.0 未満 | ^6.5.3 / ^7.4.0 |
| 14.0.x〜14.3.x | ^14.15.0 / ^16.10.0 | 4.6.2 以上(14.0・14.1 は 4.8.0 未満、14.2 以降は 4.9.0 未満) | ^6.5.3 / ^7.4.0 |
| 13.0.x〜13.4.x | ^12.20.0 / ^14.15.0 / ^16.10.0 | 13.0 は ~4.4.3、13.1・13.2 は 4.6.0 未満、13.3 以降は 4.7.0 未満 | ^6.5.3 / ^7.4.0 |
| 12.0.x〜12.2.x | ^12.14.0 / ^14.15.0 | 12.0 は ~4.2.3、12.1 以降は 4.2.3 以上 4.4.0 未満 | ^6.5.3(12.2 は ^7.0.0 も可) |
| 11.x | ^10.13.0 / ^12.11.0 | 11.0 は ~4.0.0、11.1 以降は 4.0.0 以上 4.2.0 未満 | ^6.5.3 |
| 10.x | ^10.13.0 / ^12.11.0 | 10.0 は ~3.9.0、10.1 以降は 3.9.0 以上 4.1.0 未満 | ^6.5.3 |
| 9.x | ^10.13.0 / ^12.11.0 | 9.0 は 3.6.0 以上 3.8.0 未満、9.1 は 3.6.0 以上 3.9.0 未満 | ^6.5.3 |
表の読み方: たとえば Angular 17 から 18 へ上げるなら、Node.js は 18.19.1 以上(または 20.11.1 以上)が必要です。Node.js 18.13 のままでは v18 の条件を満たさないので、先に Node.js を上げてから ng update を実行します。TypeScript は ng update が package.json を書き換えてくれるので、自分で先に上げる必要はありません(今回の v22 への更新でも 5.9.3 から 6.0.3 へ自動で変わりました)。
サポート期間
公式ドキュメントの「Angular versioning and releases」のページを開くと、2026年10月3日時点で次の3つがサポート対象でした。
| バージョン | 状態 | リリース日 | LTS の終了 |
|---|---|---|---|
| v22 | Active | 2026-06-03 | 2028-06 |
| v21 | LTS | 2025-11-19 | 2027-06 |
| v20 | LTS | 2025-05-28 | 2026-11-28 |
同じページには「複数のメジャーをまたぐときは1メジャーずつ更新する」と書かれており、ng update 自身もこれをチェックします。
準備:今の版を確かめてコミットする
この記事で使うのは、ng new shop で作った Angular 20 のプロジェクトに、以前の書き方(NgModule、*ngIf、コンストラクタでの注入、@Input など)のコンポーネントを足したものです。移行でどう書き換わるかを見るために、わざと次のようなコードにしてあります。
// src/app/tasks/task-list.component.ts(更新前)
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { TaskService } from './task.service';
@Component({
selector: 'app-task-list',
standalone: false,
template: `
<h2 #heading [ngClass]="{ done: tasks.length === 0 }">{{ title }}</h2>
<p *ngIf="tasks.length === 0; else list">タスクはありません</p>
<ng-template #list>
<ul>
<li *ngFor="let t of tasks; let i = index">
{{ i + 1 }}. {{ t }}
<button (click)="picked.emit(t)">選ぶ</button>
</li>
</ul>
</ng-template>
<app-task-badge [count]="tasks.length"></app-task-badge>
`,
})
export class TaskListComponent {
@Input() title = 'タスク';
@Output() picked = new EventEmitter<string>();
@ViewChild('heading') heading!: ElementRef<HTMLHeadingElement>;
tasks: string[];
constructor(private taskService: TaskService) {
this.tasks = this.taskService.list();
}
}
このコンポーネントは TaskModule(NgModule)の declarations に登録してあります。更新前の依存は @angular/core などが ^20.3.0、@angular/cli が ^20.3.37、typescript が ~5.9.2、rxjs が ~7.8.0 です。
作業は専用のブランチで行い、更新のたびにコミットします。 ng update は多くのファイルを書き換えるので、コミットで区切っておくと差分の確認と取り消しが楽になります。
手順1:ng update で上げられる版を一覧する
引数なしの ng update は、何も書き換えずに「更新できるパッケージ」だけを表示します。
ng update
Using package manager: npm
Collecting installed dependencies...
Found 20 dependencies.
We analyzed your package.json, there are some packages to update:
Name Version Command to update
--------------------------------------------------------------------------
@angular/cli 20.3.37 -> 21.2.9 ng update @angular/cli@21
@angular/core 20.3.33 -> 21.2.9 ng update @angular/core@21
There might be additional packages which don't provide 'ng update' capabilities that are outdated.
You can update the additional packages by running the update command of your package manager.
v22 が出ているのに、表示されるのは 次のメジャーである v21 です。「Command to update」の列がそのまま次に打つコマンドになります。なお、この一覧では 21.2.9 と表示されましたが、次の手順で入ったのは 21.2.24 と 21.2.25 でした。一覧の数字は目安で、実際に入る版は更新時の出力で確かめる のが確実です。
手順2:v20 から v21 へ上げる
@angular/core と @angular/cli は必ずセットで指定します。
ng update @angular/core@21 @angular/cli@21
The installed Angular CLI version is outdated.
Installing a temporary Angular CLI versioned 21.2.24 to perform the update.
Using package manager: npm
Collecting installed dependencies...
Found 20 dependencies.
Fetching dependency metadata from registry...
Updating package.json with dependency @angular/build @ "21.2.24" (was "20.3.37")...
Updating package.json with dependency @angular/cli @ "21.2.24" (was "20.3.37")...
Updating package.json with dependency @angular/compiler-cli @ "21.2.25" (was "20.3.33")...
Updating package.json with dependency @angular/core @ "21.2.25" (was "20.3.33")...
(ほかの @angular パッケージも同じ 21.2.25 へ)
UPDATE package.json (1136 bytes)
✔ Cleaning node modules directory
✔ Installing packages
** Executing migrations of package '@angular/cli' **
❯ Remove any karma configuration files that only contain the default content.
Migration completed (No changes made).
❯ Update 'moduleResolution' to 'bundler' in TypeScript configurations.
Migration completed (No changes made).
❯ Updates the 'lib' property in tsconfig files to use 'es2022' or a more modern version.
Migration completed (No changes made).
** Optional migrations of package '@angular/cli' **
This package has 1 optional migration that can be executed.
❯ Migrate application projects to the new build system.
ng update @angular/cli --name use-application-builder
** Executing migrations of package '@angular/core' **
❯ Adds `BootstrapContext` to `bootstrapApplication` calls in `main.server.ts` to support server rendering.
Migration completed (No changes made).
❯ Moves imports of `ApplicationConfig` from `@angular/platform-browser` to `@angular/core`.
Migration completed (No changes made).
❯ Migrates deprecated bootstrap options to providers.
Migration completed (No changes made).
❯ Converts the entire application to block control flow syntax.
UPDATE src/app/tasks/task-list.component.ts (966 bytes)
Migration completed (1 file modified).
❯ Ensures that the Router.lastSuccessfulNavigation signal is now invoked.
Migration completed (No changes made).
** Optional migrations of package '@angular/core' **
This package has 1 optional migration that can be executed.
❯ Replaces usages of the deprecated Router.getCurrentNavigation method with the Router.currentNavigation signal.
ng update @angular/core --name router-current-navigation
出力の読み方
| 出力の部分 | 意味 |
|---|---|
Installing a temporary Angular CLI versioned 21.2.24 |
更新先の CLI を一時的に取得して、その CLI が更新を進める |
Updating package.json with dependency ... |
package.json の書き換え(was が更新前の版) |
** Executing migrations of package ... ** |
必ず実行される migration。1件ずつ結果が出る |
Migration completed (No changes made). |
対象のコードがなく、何も変わらなかった |
UPDATE src/... (966 bytes) |
そのファイルが書き換えられた |
** Optional migrations of package ... ** |
任意の migration。下に書かれたコマンドであとから実行できる |
v21 への更新で書き換わったコード
今回の v21 への更新では、制御フローの移行(*ngIf や *ngFor をブロック構文へ)が自動で実行 されました。git diff で見ると、テンプレートが次のように変わっています。
- <p *ngIf="tasks.length === 0; else list">タスクはありません</p>
- <ng-template #list>
+ @if (tasks.length === 0) {
+ <p>タスクはありません</p>
+ } @else {
<ul>
- <li *ngFor="let t of tasks; let i = index">
- {{ i + 1 }}. {{ t }}
- <button (click)="picked.emit(t)">選ぶ</button>
- </li>
+ @for (t of tasks; track t; let i = $index) {
+ <li>
+ {{ i + 1 }}. {{ t }}
+ <button (click)="picked.emit(t)">選ぶ</button>
+ </li>
+ }
</ul>
- </ng-template>
+ }
else 用の ng-template がなくなり、@for には track t が補われました。track に何を使うかは自動では判断しきれないので、一覧の要素に id があるなら track t.id のように見直す とよいでしょう。
更新が終わったら、版の確認・ビルド・コミットを行います。
ng version
ng build
git add -A && git commit -m "ng update to v21"
版の表示(抜粋)とビルドの結果です。
Angular CLI : 21.2.24
Angular : 21.2.25
Node.js : 22.22.2
│ typescript │ 5.9.3 │ ~5.9.2 │
Application bundle generation complete. [18.284 seconds]
Output location: /home/taro/shop/dist/shop
手順3:v21 から v22 へ上げる
同じ要領で次のメジャーへ進みます。今回は Node.js 22.22.2 のまま実行したところ、Node.js の版が足りないというメッセージで止まりました。
ng update @angular/core@22 @angular/cli@22
The installed Angular CLI version is outdated.
Installing a temporary Angular CLI versioned 22.2.1 to perform the update.
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.
Please update your Node.js version or visit https://nodejs.org/ for additional instructions.
対応表のとおり、Angular 22 は Node.js 22.22.3 以上(または 24.15.0 以上、26.0.0 以上)が条件です。パッチ番号が1つ足りないだけでも止まるので、メジャーを上げる前に対応表の Node.js の欄を見ておく と手戻りがありません。Node.js を 24.21.0 に切り替えて、もう一度実行した結果が次です。
The installed Angular CLI version is outdated.
Installing a temporary Angular CLI versioned 22 to perform the update.
Using package manager: npm
Collecting installed dependencies...
Found 20 dependencies.
Fetching dependency metadata from registry...
Updating package.json with dependency @angular/common to version 22.2.1...
Updating package.json with dependency @angular/core to version 22.2.1...
(@angular/forms・router・build・cli など、ほかの @angular パッケージも同じ 22.2.1 へ)
Updating package.json with dependency typescript to version 6.0.3...
✔ Cleaning node modules directory
✔ Installing packages
** 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/tasks/task-list.component.ts (1041 bytes)
UPDATE src/app/tasks/task-badge.component.ts (331 bytes)
UPDATE src/app/app.ts (533 bytes)
Migration completed (3 files modified).
❯ Adds 'withXhr' to 'provideHttpClient' function calls when the 'HttpXhrBackend' is used.
For more information see: https://angular.dev/api/common/http/withXhr
Migration completed (No changes made).
v22 への更新で書き換わったコード
- "typescript": "~5.9.2"
+ "typescript": "~6.0.3"
-import { Component, signal } from '@angular/core';
+import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-root',
imports: [TaskModule],
+ changeDetection: ChangeDetectionStrategy.Eager,
TypeScript が 6.0.3 に上がり、すべてのコンポーネントに changeDetection: ChangeDetectionStrategy.Eager が足されました。 これは「更新前と同じ変更検知の動きを保つ」ための書き換えで、3つのコンポーネントすべてに入りました。動作を変えないための1行なので、まずはそのままビルドとテストを通し、変更検知の方式を見直すのは別の作業として分けるのがおすすめです。
更新後の確認
ng build
ng update
Application bundle generation complete. [9.275 seconds]
Using package manager: npm
Collecting installed dependencies...
Found 22 dependencies.
We analyzed your package.json and everything seems to be in order. Good work!
everything seems to be in order. Good work! と出れば、Angular 本体の更新は完了です。
ng update のオプション一覧
Angular CLI 22.2.1 の ng update --help に出るオプションをまとめました。
| オプション | 意味 | 使う場面 |
|---|---|---|
--force |
peer dependency の不一致を無視する | 対応版がまだないライブラリがあり、先に本体だけ上げたいとき |
--allow-dirty |
未コミットの変更があっても更新する | 基本は使わず、コミットしてから実行する |
--create-commits(-C) |
更新と migration ごとにコミットを作る | 差分を細かく残したいとき |
--migrate-only |
版は変えず migration だけ実行する | 手で版を上げたあとに migration を流すとき |
--name |
実行する migration を名前で指定する | 任意の migration をあとから流すとき |
--from / --to |
migration を適用する版の範囲(--migrate-only と併用) |
手動で版を上げた範囲を指定するとき |
エラー文と対処の一覧
今回の更新作業で実際に出たメッセージを、原因と対処つきでまとめます。
| 出たメッセージ(先頭) | 原因 | 対処 |
|---|---|---|
Error: Repository is not clean. Please commit or stash any changes before updating. |
未コミットの変更がある | git commit か git stash をしてから再実行 |
Updating multiple major versions of '@angular/core' at once is not supported. |
メジャーを2つ以上飛ばした | メッセージに出る ng update @angular/core@21 の順に1つずつ |
The Angular CLI requires a minimum Node.js version of ... |
Node.js が更新先の条件に届いていない | 対応表を見て Node.js を先に上げる |
Package "..." has an incompatible peer dependency to "@angular/common" |
入っているライブラリが新しい Angular に対応していない | ライブラリを対応版へ上げる/外す/--force |
npm error ERESOLVE unable to resolve dependency tree |
npm が peer dependency を解決できない | 今の Angular に合う版を指定して入れる |
A DOM environment is required for non-browser tests. |
Vitest へ移行後、jsdom などが入っていない | npm i -D jsdom |
TS1005: 'from' expected. |
migration の書き換えが重なって入った | 該当行を手で直す(後述) |
Repository is not clean
ファイルを編集したまま ng update を実行すると、次の1行で止まります。
Error: Repository is not clean. Please commit or stash any changes before updating.
git status --short で残っている変更を確かめ、コミットしてからやり直します。
--allow-dirty で通すこともできますが、更新による差分と自分の変更が混ざって見分けにくくなります。先にコミットするほうが、あとで差分を読みやすくなります。
Updating multiple major versions at once is not supported
v20 のプロジェクトで、いきなり v22 を指定した結果です。
Updating multiple major versions of '@angular/core' at once is not supported. Please migrate each major version individually.
Run 'ng update @angular/core@21' in your workspace directory to update to latest '21.x' version of '@angular/core'.
For more information about the update process, see https://update.angular.dev/?v=20.0-21.0
次に打つコマンドがメッセージの中に書かれているので、そのとおりに1つずつ進めます。
incompatible peer dependency
Angular に依存するライブラリが入っていると、更新先の版と合わない場合にここで止まります。今回は Angular 21 向けの ngx-toastr 20.0.5(peer dependency が @angular/common ^21.0.0)を入れた状態で v22 へ上げようとしました。
Fetching dependency metadata from registry...
Package "ngx-toastr" has an incompatible peer dependency to "@angular/common" (requires "^21.0.0", would install "22.2.1").
Incompatible peer dependencies found. See above for details. You can bypass this check using the --force option.
対処は次の順で考えます。
| 状況 | 対処 | コマンド例 |
|---|---|---|
| ライブラリに新しい Angular 対応版がある | ライブラリも一緒に上げる | npm view ライブラリ名 peerDependencies で確認してから更新 |
| 対応版がまだない/なくても困らない | いったん外して本体を上げる | npm uninstall ngx-toastr |
| 対応版はないが動作に影響しないとわかっている | チェックを飛ばす | ng update @angular/core@22 @angular/cli@22 --force |
今回は npm uninstall ngx-toastr で外してコミットし、そのあと v22 への更新が通りました。npm view パッケージ名 peerDependencies で対応範囲を先に調べておく と、どの対処を選ぶか判断しやすくなります。
ERESOLVE unable to resolve dependency tree
こちらは npm install 側のエラーです。Angular 21 のプロジェクトに、Angular 22 向けの版しか最新にないライブラリを版指定なしで入れると出ます。
npm install @ng-select/ng-select
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: shop@0.0.0
npm error Found: @angular/common@21.2.25
npm error node_modules/@angular/common
npm error @angular/common@"^21.2.25" from the root project
npm error
npm error Could not resolve dependency:
npm error peer @angular/common@"^22.0.0 || ^23.0.0" from @angular/cdk@22.2.1
npm error node_modules/@angular/cdk
npm error peer @angular/cdk@"^22.0.0" from @ng-select/ng-select@24.2.0
npm error node_modules/@ng-select/ng-select
npm error @ng-select/ng-select@"*" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
読み方は「Found: が今入っている版」「Could not resolve dependency: が相手の求める版」です。ここでは @ng-select/ng-select@24.2.0 が Angular 22 を求めているのに、プロジェクトは 21.2.25 なので合いません。今の Angular のメジャーに合う版を指定して入れる のが基本の直し方です。--legacy-peer-deps は検査を省くだけなので、合わない組み合わせがそのまま入る点を理解したうえで使います。
移行schematicのコマンド早見表
ng update が自動で行う migration とは別に、書き方を新しい API へそろえるための schematic が用意されています。インストールした @angular/core 22.2.1 の schematics/collection.json に載っている15件を、コマンドつきでまとめました。右端の列は、この記事のプロジェクトで流したときの結果です。
| 移行の内容 | コマンド | 今回の結果 |
|---|---|---|
| NgModule から standalone へ | ng generate @angular/core:standalone |
2つのモードを実行。3ファイル更新、NgModule を削除 |
*ngIf などを制御フロー構文へ |
ng generate @angular/core:control-flow |
v21 への ng update で自動実行。1ファイル更新 |
コンストラクタ注入を inject() へ |
ng generate @angular/core:inject |
1ファイル更新 |
@Input を signal 入力へ |
ng generate @angular/core:signal-input-migration |
Migrated 2/2 inputs |
@Output を output() へ |
ng generate @angular/core:output-migration |
Migrated 1 out of 1 detected outputs |
@ViewChild などを signal クエリへ |
ng generate @angular/core:signal-queries-migration |
Migrated 1/1 queries(重複行を手で修正) |
| 上の signal 系3つをまとめて | ng generate @angular/core:signals |
今回は個別に実行 |
| 使っていない imports を消す | ng generate @angular/core:cleanup-unused-imports |
対象なし |
| 閉じタグを自己終了タグへ | ng generate @angular/core:self-closing-tag |
1コンポーネント更新 |
ngClass を class バインディングへ |
ng generate @angular/core:ngclass-to-class |
Migrated 0(オブジェクト形式の1件は対象外) |
ngStyle を style バインディングへ |
ng generate @angular/core:ngstyle-to-style |
今回は実行対象外 |
CommonModule を個別の import へ |
ng generate @angular/core:common-to-standalone |
Migrated 0 |
| ルートを遅延読み込みへ | ng generate @angular/core:route-lazy-loading |
今回は実行対象外 |
RouterTestingModule を置き換え |
ng generate @angular/core:router-testing-module-migration |
今回は実行対象外 |
@Injectable を @Service へ |
ng generate @angular/core:service |
今回は実行対象外 |
テストとビルドに関する移行は、CLI 側(@schematics/angular)にあります。
| 移行の内容 | コマンド | 今回の結果 |
|---|---|---|
| Karma のテスト設定を Vitest へ | ng update @angular/cli --name migrate-karma-to-vitest |
3ファイル更新、vitest を追加 |
| Jasmine の書き方を Vitest へ | ng generate @schematics/angular:refactor-jasmine-vitest |
1ファイル変換 |
| 新しいビルドシステムへ | ng update @angular/cli --name use-application-builder |
今回は実行対象外 |
schematic は1つ流すごとにビルドしてコミットする と、どの移行で何が変わったかを切り分けられます。対話なしで流したいときは --interactive=false を付けます。
standalone への移行(2回に分けて実行)
standalone の schematic は --mode で段階を選びます。今回使ったのは次の2つです。
ng generate @angular/core:standalone --mode=convert-to-standalone --path=./ --interactive=false
ng generate @angular/core:standalone --mode=prune-ng-modules --path=./ --interactive=false
🎉 Automated migration step has finished! 🎉
IMPORTANT! Please verify manually that your application builds and behaves as expected.
UPDATE src/app/tasks/task-list.component.ts (1174 bytes)
UPDATE src/app/tasks/task-badge.component.ts (316 bytes)
UPDATE src/app/tasks/task.module.ts (355 bytes)
🎉 Automated migration step has finished! 🎉
DELETE src/app/tasks/task.module.ts
UPDATE src/app/app.ts (556 bytes)
1回目でコンポーネントから standalone: false が外れて imports が足され、2回目で不要になった task.module.ts が削除されました。app.ts の imports: [TaskModule] は imports: [TaskListComponent] に変わっています。
inject・signal入力・output・signalクエリ
ng generate @angular/core:inject --path=./ --interactive=false
ng generate @angular/core:signal-input-migration --interactive=false
ng generate @angular/core:output-migration --interactive=false
ng generate @angular/core:signal-queries-migration --interactive=false
Successfully migrated to signal inputs 🎉
-> Migrated 2/2 inputs.
UPDATE src/app/tasks/task-badge.component.ts (318 bytes)
UPDATE src/app/tasks/task-list.component.ts (1205 bytes)
Successfully migrated to outputs as functions 🎉
-> Migrated 1 out of 1 detected outputs (100.00 %).
UPDATE src/app/tasks/task-list.component.ts (1180 bytes)
すべて流し終えたあとのコンポーネントが次です(重複行を直したあとの形)。
// src/app/tasks/task-list.component.ts(移行後)
import { Component, ElementRef, ChangeDetectionStrategy, inject, input, output, viewChild } from '@angular/core';
import { TaskService } from './task.service';
import { NgClass } from '@angular/common';
import { TaskBadgeComponent } from './task-badge.component';
@Component({
selector: 'app-task-list',
changeDetection: ChangeDetectionStrategy.Eager,
template: `
<h2 #heading [ngClass]="{ done: tasks.length === 0 }">{{ title() }}</h2>
@if (tasks.length === 0) {
<p>タスクはありません</p>
} @else {
<ul>
@for (t of tasks; track t; let i = $index) {
<li>
{{ i + 1 }}. {{ t }}
<button (click)="picked.emit(t)">選ぶ</button>
</li>
}
</ul>
}
<app-task-badge [count]="tasks.length"></app-task-badge>
`,
imports: [NgClass, TaskBadgeComponent],
})
export class TaskListComponent {
private taskService = inject(TaskService);
readonly title = input('タスク');
readonly picked = output<string>();
readonly heading = viewChild.required<ElementRef<HTMLHeadingElement>>('heading');
tasks: string[];
constructor() {
this.tasks = this.taskService.list();
}
}
更新前と比べると、次のように変わっています。
| 更新前 | 移行後 | 担当した schematic |
|---|---|---|
standalone: false と NgModule |
imports: [NgClass, TaskBadgeComponent] |
standalone |
constructor(private taskService: TaskService) |
private taskService = inject(TaskService) |
inject |
@Input() title = 'タスク' |
readonly title = input('タスク')、テンプレートは title() |
signal-input-migration |
@Input({ required: true }) count!: number |
readonly count = input.required<number>() |
signal-input-migration |
@Output() picked = new EventEmitter<string>() |
readonly picked = output<string>() |
output-migration |
@ViewChild('heading') heading!: ElementRef<...> |
readonly heading = viewChild.required<ElementRef<...>>('heading') |
signal-queries-migration |
テンプレート側の {{ title }} が {{ title() }} に書き換わっている点に注目してください。 signal 入力は関数として読むので、参照している箇所もまとめて直してくれます。
書き換えが重なって入った例と直し方
今回、signal-queries-migration を流した直後のファイルは、次のように 同じ内容が2回続けて書き込まれていました(同じ手順でやり直しても同じ結果でした)。
import { Component, ElementRef, ChangeDetectionStrategy, inject, input, output, viewChild }{ Component, ElementRef, ChangeDetectionStrategy, inject, input, output, viewChild } from '@angular/core';
viewChild.required の行も、同じように2回続けて入っていました。schematic の出力にも Successfully migrated to signal queries 🎉 が2回出ていました。このまま ng build を実行すると、次のエラーで知らせてくれます。
Application bundle generation failed. [2.284 seconds]
✘ [ERROR] Expected "from" but found "{"
src/app/tasks/task-list.component.ts:1:91:
1 │ ...ject, input, output, viewChild }{ Component, ElementRef, ChangeD...
│ ^
╵ from
✘ [ERROR] TS1005: 'from' expected. [plugin angular-compiler]
直し方は、重なった部分を1つ消すだけです。消したあとの ng build は通りました。schematic の出力に「成功」と出ても、必ずビルドして確かめる。 この一手間で、こうした書き換えの重なりにすぐ気づけます。コミットを分けてあれば、git diff で重なった行もひと目でわかります。
テストを Karma・Jasmine から Vitest へ移す
v22 の ng update では、テストランナーは Karma のまま残りました。Vitest へ移すのは任意の migration です。
ng update @angular/cli --name migrate-karma-to-vitest
- shop
Projects skipped (non-applications): 0
Projects skipped (missing application builder): 0
Note: To refactor your test files from Jasmine to Vitest, consider running the following command:
ng g @schematics/angular:refactor-jasmine-vitest <project_name>
-----------------------------------------
UPDATE tsconfig.spec.json (441 bytes)
UPDATE angular.json (2416 bytes)
UPDATE package.json (1151 bytes)
✔ Packages installed successfully.
Migration completed (3 files modified).
angular.json の test の設定は次のように変わりました。
- "builder": "@angular/build:karma",
+ "builder": "@angular/build:unit-test",
"options": {
+ "buildTarget": ":build:testing",
+ "runner": "vitest"
続けて、出力で案内されたコマンドでテストコードの書き方を移します。
ng generate @schematics/angular:refactor-jasmine-vitest --interactive=false
Jasmine to Vitest Refactoring Summary:
- 1 test file(s) scanned.
- 1 file(s) transformed.
CREATE jasmine-vitest-20261003T103312756Z.md (350 bytes)
UPDATE src/app/app.spec.ts (792 bytes)
- const spy = jasmine.createSpy('picked');
+ const spy = vi.fn().mockName('picked');
jasmine.createSpy が vi.fn().mockName(...) に変わり、変換の結果をまとめた Markdown のレポート(スキャン数・変換数・手作業が必要な TODO の数)も作られました。この状態で ng test を実行すると、次のメッセージが出ます。
The following packages are required but were not found:
- A DOM environment is required for non-browser tests. Please install either "jsdom" or "happy-dom".
Please install the missing packages and rerun the test command.
メッセージのとおり jsdom を入れると、テストが通りました。
npm i -D jsdom
ng test --watch=false
RUN v5.0.3 /home/taro/shop
Test Files 1 passed (1)
Tests 2 passed (2)
よくある質問
Q. v18 から v22 まで一気に上げられますか?
A. ng update はメジャーを2つ以上飛ばす指定を受け付けません。今回も v20 から v22 を指定したところ「Updating multiple major versions of '@angular/core' at once is not supported.」と出て、次に打つコマンド(ng update @angular/core@21)が案内されました。v18 → v19 → v20 → v21 → v22 と順に進め、各段階でビルドとコミットを挟みます。
Q. TypeScript は自分で上げますか?
A. ng update が対応する版へ書き換えます。今回は v22 への更新で ~5.9.2 から ~6.0.3 へ変わりました。RxJS は 7.8.2 のままでした。
Q. 移行 schematic は全部流す必要がありますか?
A. 必須ではありません。ng update が自動で実行する migration だけで、新しい版でビルドできる状態になります。standalone や signal 入力などの schematic は、書き方をそろえたいときに1つずつ流せば十分です。今回のように書き換えが重なって入ることもあるので、流すたびにビルドします。
まとめ
- メジャーは1つずつ。
ng update @angular/core@次 @angular/cli@次を順に実行し、飛ばすと案内のメッセージで止まる - 始める前にコミット。 未コミットの変更があると「Repository is not clean」で止まる
- 対応表で Node.js を先に確かめる。 Angular 22 は Node.js 22.22.3 以上・24.15.0 以上・26.0.0 以上が条件
- 出力の
UPDATEとgit diffを読む。 v21 では制御フロー、v22 ではChangeDetectionStrategy.Eagerの追記と TypeScript 6.0 への変更が自動で入った - peer dependency で止まったら、ライブラリを対応版へ上げる・外す・
--forceの順に検討する - 移行 schematic は1つ流すごとにビルドする。 成功の表示が出ても、書き換えが重なることがある
- 最後に
ng updateを実行し、「everything seems to be in order」を確かめる
参考・出典
- Angular 公式ドキュメント「Version compatibility」 https://angular.dev/reference/versions
- Angular 公式ドキュメント「Angular versioning and releases」 https://angular.dev/reference/releases
- Angular 公式ドキュメント「Migrations」 https://angular.dev/reference/migrations
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Angularバージョンアップ手順|ng updateとエラー対処一覧」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。