Marginalia

Angular: provideRouter によるルーティング設定 (v14.2)

Angular v14.2で追加された Routerパッケージの provideRouter APIは、 RouterModule.forRoot を使わずにルーティング設定を行うことができる。このAPIはアプリケーションがスタンドアロンコンポーネントを使っていなくても使用できる。

というわけで、 NgModule ベースの従来からのアプリケーションとスタンドアロンAPIベースのアプリケーションの両方で、この新しい provideRouter APIの使用例を紹介する。

NgModuleベースのアプリケーションでの使用例

従来からの NgModule ベースのアプリケーションでは、これまで RouterModule.forRootimports 配列に追加していたコードを、 provideRouter の戻り値を providers 配列に追加するように書き換えればよい。

ただし、 routerLink<router-outlet> などのディレクティブをコンポーネントのHTMLテンプレートで使うにはそれらをエクスポートしている RouterModule が必要なので、 imports 配列には素の RouterModule が残るだろう。

import { BrowserModule } from '@angular/platform-browser';
import { provideRouter, RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { routes } from './app.routing';
import { Page1Component } from './page1.component';
import { Page2Component } from './page2.component';

@NgModule({
  // import RouterModule for templates (router directives)
  imports: [BrowserModule, RouterModule],
  // provide Router with routes
  providers: [provideRouter(routes)],
  declarations: [AppComponent, Page1Component, Page2Component],
  bootstrap: [AppComponent],
})
export class AppModule {}

スタンドアロンAPIベースのアプリケーションでの使用例

NgModule を持たないスタンドアロンAPIベースのアプリケーションでは、 bootstrapApplication 関数のオプションで providers 配列に provideRouter の戻り値を渡せばよい。

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideRouter } from '@angular/router';
import { routes } from './app/app.routing';

bootstrapApplication(AppComponent, {
  // provide Router with routes
  providers: [provideRouter(routes)],
});

RouterFeaturesによるオプション設定

これまで RouterModule.forRoot の第2引数で設定していたオプションは、 provideRouter では可変長配列になっている第2引数以降に RouterFeature 型のオブジェクトを渡して設定する。 RouterFeature オブジェクトの生成は withXXX という命名の関数が用意されており、その戻り値を渡す。

import {provideRouter, withDebugTracing, withRouterConfig} from '@angular/router';

bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(
				appRoutes,
        withDebugTracing(),
        withRouterConfig({ 
					paramsInheritanceStrategy: 'always' 
				})),
    ],
  },
);

これまでは単一のオブジェクトにすべてのオプションを指定していたが、新しいAPIでは個別のオプションごとに独立した RouterFeature として定義される。 RouterFeature を返す関数の一覧は APIレファレンスから確認できる。

遅延ロード用の provideRoutes

provideRouterRouterModule.forRoot に対応するAPIなので、当然 RouterModule.forChild に対応するものもある。それが provideRoutes APIだ。

遅延読み込みさせる子モジュールの providers 配列に provideRoutes 関数の戻り値を渡すことで RouterModule.forChild と同等の設定ができる。

@NgModule({
  providers: [
    provideRoutes([
      {
        path: '',
        pathMatch: 'full',
        component: PageLazyComponent,
      },
    ]),
  ],
  declarations: [PageLazyComponent],
})
export class LazyLoadedModule {}

あとはこのモジュールを loadChildren に指定するといい。

export const routes: Route[] = [
  {
    path: 'lazy',
    loadChildren: () =>
      import('./lazy/lazy.module').then((m) => m.LazyLoadedModule),
  },
  // ...
];

ちなみに、スタンドアロンAPIベースであればそもそも Route[] 型のオブジェクトを loadChildren に渡せばいいため、 provideRoutes APIは必要ない。

// lazy/lazy.routing.ts
export const routes: Route[] = [
  {
    path: '',
    pathMatch: 'full',
    component: PageLazyComponent,
  },
];

// app.routing.ts
export const routes: Route[] = [
  {
    path: 'lazy',
    loadChildren: () => import('./lazy/lazy.routing').then((m) => m.routes),
  },
  // ...
];

既存アプリを provideRouter に置き換えるべきか?

RouterやHttpClient、Commonなど、Angularのビルトインパッケージは脱 NgModule の対応が着々と進められているが、基本的にスタンドアロンAPIは NgModule と互換性が保たれているため、既存アプリがスタンドアロンAPIベースへ置き換えることを急ぐ必要はない。

ただし、 provideRouter に関してはスタンドアロンAPIベースであるなしにかかわらず RouterModule.forRoot を置き換えられるAPIとして提供されている。つまり、現在は同じ用途のAPIが2つ存在しており、これはAngularのフレームワークとしての基本的な原則に反している。これが許されているのは provideRouter APIがまだデベロッパープレビュー版だからだろう。

したがって、 provideRouter が安定APIとしてリリースされるときには、おそらく RouterModule.forRoot が代わりに非推奨となることが予想される( RouterModule 自体はディレクティブのエクスポートのために残されるだろう)。置き換えを急ぐ必要はないが、1〜2年以内にあるかもしれない変更として意識しておくと驚かずに済むのではなかろうか。