とんかつ時々あんどーなつ

〜たとえ低空でも飛行していられるように〜

Standalone based Tour of Heroes

はじめに

これは Angular チュートリアル Tour of Heroes を今話題の Standalone base で書いたときのメモです。 angular/core のバージョンは v14.2.5 です。

Standalone とはなにかについては以下を見たほうがわかるので割愛します。

standalone への修正

コンポーネント

v14 では何も設定しないと CLI で作ったコンポーネントは standalone true になっていない。すべて standalone component で作るつもりであれば angular.json で設定を変更しておいたほうが楽になる。

"projectType": "application",
  "schematics": {
    "@schematics/angular:component": {
      "standalone": true
    },
    ...
  }
  ...
}

この設定を入れることで CLI で作ったコンポーネントは standalone true で作成される。

AppComponent

AppComponent を standalone component にする手順は以前 Angular v14 standalone component を試す - とんかつ時々あんどーなつ にも書いた。 再掲するが修正が必要になるのは main.ts であり、今まで bootstrapModule() を使っていた部分を修正する。

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

修正後は以下になる。

bootstrapApplication(AppComponent);

これにより AppModule が不要になるのでアプリケーションから削除することができる。

ルーティング

次はルーティングの設定を NgModule を使わない形に修正する。まずは従来の設定をおさらいする。RouterModule を使っている場合の src/app/routes.ts、src/app/app-routing.module.ts、src/main.ts は次のようになる。

// src/app/routes.ts

import { Routes } from '@angular/router';
import { DashboardPageComponent } from './features/dashboard/pages/dashboard/dashboard.component';
import { HeroDetailPageComponent } from './features/hero-detail/pages/hero-detail/hero-detail.component';
import { HeroesPageComponent } from './features/heroes/pages/heroes/heroes.component';

export const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard', component: DashboardPageComponent },
  { path: 'detail/:id', component: HeroDetailPageComponent },
  { path: 'heroes', component: HeroesPageComponent },
];
// src/app/app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { routes } from './routes';

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}
// src/main.ts

...
bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(AppRoutingModule),
  ],
});

ここから RouterModule を外していく。v14.2 で RouterModule.forRoot() の代わりになる provideRouter() という API が追加された。provideRouter() の詳細を知るには Angular: provideRouter によるルーティング設定 (v14.2) | lacolaco/tech を見ていただくのがよい。 provideRouter() を使うと main.ts が以下のように変更でき、app-routing.module.ts が不要になる。

// src/main.ts

...
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
  ],
});

router directive

RouterModule はルーティングの設定以外に <router-outlet>routerLink を使う際にも必要となる。そのためすべての RouterModule をアプリケーションから削除するにはもうひと手間必要となる。

router の API には RouterOutletRouterLinkWithHref などのディレクティブが存在するが、これらが v14.2 で standalone として提供されるようになっている。 よって、これらのディレクティブを必要なコンポーネントで import することで、すべての RouterModule をアプリケーションから削除することができる。

common directive / common pipe

おおよそすべてのコンポーネントで import されている CommonModule がある。最後にこれを削除する。CommonModule は頻繁に使用することになる NgIfNgForOf などのディレクティブや AsyncPipe などの組み込みパイプを提供している。これらのディレクティブやパイプも v14.1 で standalone として提供されているので、置き換えることができる。

ただし、想像以上に CommonModule がいろんなディレクティブ / パイプを一括で import していたので、これを外すのは無理しなくてもよさそう。 COMMON_DIRECTIVESAPI として提供されるのを待ちたいところである。

standalone にできなかったもの

ここまで修正してきた Tour of Heroes アプリケーションの中にはまだ NgModule が残っている。HttpClientModule と FormsModule である。provideRouter() のような API を期待したが angular/CHANGELOG.md at main · angular/angular · GitHub を見てもまだそれらしい変更はなかった。

※ 10/11 追記: provideHttpClient() は v15 で入りそう(https://github.com/angular/angular/pull/47502)

まとめ

Angular チュートリアルの Tour of Heroes を Standalone base で書き直してみました。コンポーネントをすべて standalone で書くことは何も不都合はなく NgModule がなくなったことで、依存の把握がしやすくなったように思います。 ルーティングの設定に関しても provideRouter() を使うことでかなりシンプルに書けるようになったので積極的に置き換える意味がありそうです。

一方で HttpClient や Forms で見たようにすべての API が standalone に対応しているわけではないのと CommonModule がに基本的なディレクティブやパイプを一括で import していたお手軽さなどを考慮すると、無理に NgModule を外すというよりは使いたいと思ったところから standalone にしていくのがよさそうです。

今回試したコード: GitHub - kasaharu/angular-architecture at standalone-based