Angular12＋Nxで作成されたWEBサイトをAngular17にアップデート

# はじめに
Angular12で作成されたWEBサイトを最新（Angular17）にアップデートする機会があったので、ソースと手順を残しておく。
Nxを使ったモノレポ構成だったこともあり、簡単にはいかなかった。

以下のAngular12のソースを17にバージョンアップする。
[Angular + Nest Example2](https://github.com/kyv28v/angular-nest-example2)
```
Angular CLI: 12.2.11
Node: 16.12.0
Package Manager: npm 8.1.0
```

# npm-check-updatesを使用した更新（失敗）
まずはnpm-check-updatesを試してみる。
`ncu -u`でpackage.jsonをまとめて最新にする。

```
PS C:\Work\git\angular-nest-example2> ncu -u                        
Upgrading C:\Work\git\angular-nest-example2\package.json
[====================] 62/62 100%

 @angular-devkit/build-angular            ~12.2.0  →  ~17.0.7
 @angular-eslint/eslint-plugin            ~12.3.0  →  ~17.1.1
 @angular-eslint/eslint-plugin-template   ~12.3.0  →  ~17.1.1
・
・中略
・
 typescript                                ~4.3.5  →   ~5.3.3
 zone.js                                  ~0.11.4  →  ~0.14.2

Run npm install to install new versions.
```
・・・何となく無理っぽい気はするけど、まあダメもとでやってみるか。
`npm install`だと依存関係エラーが出たので、とりあえず`npm install --legacy-peer-deps`で強引に進め、インストールは成功。
だが、実行時に、以下のエラーが発生。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285246/855c391e-e3a4-2455-59b4-41148289f050.png)

Nxの構成が大きく変わっているらしく、そのあたりをなんとかする必要がありそう。

# Nxコマンドを使用した更新（失敗）
Nxのコマンドにも自動更新があるので、今度はこちらを試してみる。
[Automate Updating Dependencies | Nx](https://nx.dev/core-features/automate-updating-dependencies)

まずは`nx migrate latest`コマンドでモジュールのバージョンを更新。これは成功した。
また`npm install --legacy-peer-deps`で強引にモジュールをインストールする。

次に`nx migrate --run-migrations`を実行したが、失敗。
```
 >  NX   Failed to run block-template-entities from @angular/core. This workspace is NOT up to date!
 >  NX   import_typescript3.default.getDecorators is not a function
```
ネットの情報をもとに`nx.json`や`project.json`などを変更してみたが、どうにも解決できず・・・。
諦めて、Nxのプロジェクトを1から作り直すことにする。


# 新規Nxプロジェクトの再作成
まずはnestの環境（api）を生成。
```
npx create-nx-workspace@latest --preset nest --name angular-nest-example3 --appName api --nxCloud true
```
作成されたプロジェクトフォルダに移動して、angularの環境（web）を生成。
```
npm install @nrwl/angular
nx generate @nrwl/angular:application --name web
```
とりあえずこれで「api」と「web」のフォルダが作成され、nx serve api、nx serve webでそれぞれ起動できる。


# ソースコピー
変更元のソースを新環境にコピーする。
`apps\api\src`、`apps\api\src`の配下は丸ごと上書きでいいはず。

`package.json`は、scripts部分は上書き。
dependenciesなどは、この後コマンドでモジュール追加していく。

その他、`tsconfig.json`などは、差分を見比べて必要な部分をマージしていく。

# モジュールの追加
変更元の`package.json`を参考に、必要なモジュールを追加していく。

```
npm install @angular/cdk --legacy-peer-deps
npm install @angular/material --legacy-peer-deps
・
・
・
以下略
```

# エラーの回避
`npm start`で起動しようとすると、大量のエラーが出てきた・・・一つずつ回避していく。

### relativeLinkResolution
`relativeLinkResolution`の定義は、Angular15あたりで不要になったらしい。

```
Error: apps/web/src/app/app-routing.module.ts:19:46 - error TS2345: Argument of type '{ relativeLinkResolution: string; }' is not assignable to parameter of type 'ExtraOptions'.
  Object literal may only specify known properties, and 'relativeLinkResolution' does not exist in type 'ExtraOptions'.

19     imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
```

↓ 単純に削除すればOK。

```typescript
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
↓
imports: [RouterModule.forRoot(routes)],
```

### entryComponents
`entryComponents`の定義もAngular15あたりで不要になったらしい。

```
Error: apps/web/src/app/app.module.ts:113:5 - error TS2345: Argument of type '{ declarations: (typeof AppComponent | typeof SimpleDialogComponent | typeof TableDialogComponent | typeof ProgressSpinnerComponent)[]; imports: (typeof BrowserModule | ... 1 more ... | ModuleWithProviders<...>)[]; providers: (typeof HttpRequestInterceptor | ... 1 more ... | { ...; })[]; bootstrap: (typeof AppCompon...' is not assignable to parameter of type 'NgModule'.
  Object literal may only specify known properties, and 'entryComponents' does not exist in type 'NgModule'.

113     entryComponents: [
        ~~~~~~~~~~~~~~~
```

↓ これも単純に削除すればOK。

```typescript
@NgModule({
    declarations: [
        AppComponent,
        SimpleDialogComponent,
        TableDialogComponent,
        ProgressSpinnerComponent,
    ],
    imports: [
        BrowserModule,
・
・　中略
・
    bootstrap: [AppComponent],
    // entryComponents: [　　　　　　　★←削除。
    //     SimpleDialogComponent,
    //     TableDialogComponent,
    // ],
})
export class AppModule {}
```


### Theming機能
Theming機能については、Angular15で大幅に仕様が変わったらしい。
インポートの時点でエラーが出ている。

```
./apps/web/src/assets/styles/styles.scss?ngGlobalStyle - Error: Module build failed (from ./node_modules/@angular-devkit/build-angular/node_modules/mini-css-extract-plugin/dist/loader.js):
HookWebpackError: Module build failed (from ./node_modules/@angular-devkit/build-angular/node_modules/sass-loader/dist/cjs.js):
Can't find stylesheet to import.
  ╷
4 │ @import '~@angular/material/theming';
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

それぞれ、以下のように変更する。

```typescript
@import '~@angular/material/theming';
@include mat-core();
↓↓↓
@use 'node_modules/@angular/material' as mat;
@include mat.core();
```

以下、darkテーマを変更したときの修正例。
メソッド名などが微妙に変わっているが、変更後のメソッドに書き直すだけでだいたいいけた。

```typescript
$dark-primary:  mat-palette($mat-blue-grey);
$dark-accent:   mat-palette($mat-amber, A200, A100, A400);
$dark-warn:     mat-palette($mat-deep-orange);
↓↓↓
$dark-primary:  mat.define-palette(mat.$blue-grey-palette);
$dark-accent:   mat.define-palette(mat.$amber-palette, A200, A100, A400);
$dark-warn:     mat.define-palette(mat.$deep-orange-palette);
```
```typescript
$dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
↓↓↓
$dark-theme: mat.define-dark-theme((
    color: (
        primary: $dark-primary,
        accent: $dark-accent,
        warn: $dark-warn,
    )
));
```
```typescript
.dark-theme {
    @include angular-material-theme($dark-theme);
}
↓↓↓
.dark-theme {
    @include mat.all-component-themes($dark-theme);
}
```
```typescript
.dark-theme-color {
    background-color : mat-color($dark-background-palette, background)!important;
    color: mat-color($dark-foreground-palette, text)!important;
}
↓↓↓
.dark-theme-color {
    background-color : mat.get-color-from-palette(mat.$dark-theme-background-palette, background)!important;
    color: mat.get-color-from-palette(mat.$dark-theme-foreground-palette, text)!important;
}
```

`mat.$blue-grey-palette`などは、デフォルトで定義されているパレット。
定義の名称などは、以下に記載されている。
node_modules\@angular\material\core\theming\_palette.scss

定義済みのパレットの色などは、以下で確認できる。
https://m1.material.io/style/color.html#color-color-palette

### その他
#### 細かいデザインの修正
微妙にデザインが崩れているところがあるので、修正していく。
特に、Angular MaterialがMDC（Material Design Component）ベースに変更されたらしく、その影響で微妙なずれが発生している個所が多かった。
これも自動で更新するコマンドがあるようだが（参考「[Migrating to MDC-based Angular Material Components](https://material.angular.io/guide/mdc-migration)」）、未確認。
一つ一つ個別に修正していく。

#### 依存関係エラー対応
`npm install`で発生していた依存関係エラーを確認する。
どうやら`angular-resize-event`というモジュールが最新では使えなくなっているらしい。
新しいバージョンも出ておらず、変わるライブラリも見当たらなかったので、`ResizeObserver`を使用する方法に書き直した。
これで`--legacy-peer-deps`しなくてもモジュールインストールできるようになった。

# 完成
これで、一通り元と同じ状態になったはず。
変更後のソースは以下の通り。
変更履歴つき。
[Angular + Nest Example3](https://github.com/kyv28v/angular-nest-example3)
```
Angular CLI: 17.0.6
Node: 20.9.0
Package Manager: npm 10.1.0
```

結局は最初から作り直したほうが早いかも、というお話でした。
（特に一気に複数バージョンアップする場合は。）