【Angular】Angular 17 へアップデートする際に実行されるマイグレーションを眺めてみる

すっかり社内では Angular アップデート芸人と化している私ですが、
今回は Angular 16 から 17 へのアップデート時に走る `angular/core` に関するマイグレーションの中身を軽く眺めてみます。
いや、17 から 18 じゃないんかーいという声もあるかと存じますが、それはまた別の記事でネタにさせていただきやす。

https://angular.dev/update-guide?v=16.0-17.0&l=1

ちなみにアップデートを行う際は、上記公式アップデートガイドに沿って進めることを推奨します。Application complexity はプロジェクトによりますが、基本的には Advanced でいつも確認するようにしています。

また 16 から 17 へのアップデートに必要な node 等の条件は以下の通り
- node.js v18.13.0 以上
- TypeScript v5.2 以上
- Zone.js v0.14.x 以上

まあ、node 以外は`ng update`する際に勝手に更新してくれるので、前もってあげておくとかは別にしなくても実はいいのです。

## 本題
では、`ng update`コマンドで `angular/core` を 17系に更新していきましょう
```
ng update @angular/core@17 @angular/cli@17
```

:::note info
`@angular-eslint/schematics`や`@angular-builders/jest`など、プロジェクトでお使いの @/angular 関連の依存関係によっては、`ng update`してあげる対象が変わってくる場合がありますので、適宜ご確認ください。
:::

以下、実行例（マイグレーション部分のみ抜粋）
```
** Executing migrations of package '@angular/core' **

❯ Angular v17 introduces a new control flow syntax that uses the @ and } characters.
  This migration replaces the existing usages with their corresponding HTML entities.
UPDATE projects/app/src/shared/header/header.component.html (2707 bytes)
  Migration completed (1 file modified).

❯ Updates `TransferState`, `makeStateKey`, `StateKey` imports from `@angular/platform-browser` to `@angular/core`.
  Migration completed (No changes made).

❯ CompilerOption.useJit and CompilerOption.missingTranslation are unused under Ivy.
  This migration removes their usage
  Migration completed (No changes made).

❯ Updates two-way bindings that have an invalid expression to use the longform expression instead.
  Migration completed (No changes made).
```

一つずつ見ていきましょ

:::note warn
対象のプロジェクトによって`angular/cli`や`cdk`のマイグレーションが含まれている場合がありますが、今回は core に絞った内容でお送りしています。
:::

## Angular v17 introduces a new control flow syntax that uses the @ and } characters.This migration replaces the existing usages with their corresponding HTML entities.

Angular 17 から新しく組み込みの制御フロー構文が追加されています。
`ngIf` `ngFor` `ngSwitch` が `@if` `@for` `@switch` で書けるようになったアレです。

https://angular.dev/guide/templates/control-flow

なんかマイグレーションメッセージの冒頭だけ見ると、新しく組み込みの制御フローに置き換えてくれてそうに見えるのですが、実際そうではなく、今後新しい制御フロー構文で `@` `{` `}` を使うので、HTMLテンプレート内で既に使われてるこれら文字列がある場合は、HTMLエンティティに置き換えるよという内容になっています。

極端な例ですが
```html
<!-- Before -->
<p>@</p>

<!-- After -->
<p>&#64;</p>
```

エディタ上でも今後は指摘してくれるようになっていますね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531606/e81e87ff-c481-3833-7100-d1aeb57d045d.png)

FYI：

https://www.angularaddicts.com/p/angular-17-new-control-flow-with-signals

ちなみに新しい制御フロー構文へのマイグレーションはちゃんと用意されているので、移行を考えている方はそちらを試してみることをおすすめする

https://qiita.com/kozy4324/items/356fd8e2429ae5142641

## Updates `TransferState`, `makeStateKey`, `StateKey` imports from `@angular/platform-browser` to `@angular/core`.

これは書いてある通り、`TransferState` `makeStateKey` `StateKey` のインポート元が`@angular/platform-browser` から `@angular/core` に変更されたので、これらのインポートを自動的に更新してくれます。

```ts
// Before
import { TransferState, makeStateKey, StateKey } from '@angular/platform-browser';

// After
import { TransferState, makeStateKey, StateKey } from '@angular/core';
```

## CompilerOption.useJit and CompilerOption.missingTranslation are unused under Ivy.

Angular の Ivy コンパイラでは、`CompilerOption.useJit` と `CompilerOption.missingTranslation` が不要になったので、設定がある場合は自動で削除してくれます。

```json5
// Before
{
  "compilerOptions": {
    "useJit": true,
    "missingTranslation": "ignore"
  }
}

// After
{
  "compilerOptions": {}
}
```

## Updates two-way bindings that have an invalid expression to use the longform expression instead.

無効な式を持つ双方向バインディングを検出し、長形式の表現に置き換えます。これにより、バインディングの正確性と可読性が向上するとのこと。ここで言ってる無効な式とは、Angular のコンパイラが正しく解釈できない場合を指しているようです。

https://github.com/angular/angular/tree/main/packages/core/schematics/migrations/invalid-two-way-bindings#readme

例を見ると、こんな感じに変換してくれるっぽいですね。

### Before
```ts
import {Component} from '@angular/core';

@Component({
  template: `<input [(ngModel)]="a && b"/>`
})
export class MyComp {}
```

### After
```ts
import {Component} from '@angular/core';

@Component({
  template: `<input [ngModel]="a && b" (ngModelChange)="a && (b = $event)"/>`
})
export class MyComp {}
```