🌟

Angular - フィーチャーモジュールの種類

2022/03/10に公開

Angular の フィーチャーモジュール

Angular を用いてそこそこな規模のアプリケーション制作を始めていくと、
すぐに AppModule がいっぱいいっぱいになってしまいます。

そこそこな規模の制作で Angular を用いる場合には、
アプリケーションを機能単位でモジュールというまとまりで管理するのが一般的です。

一般的な フィーチャーモジュールの作成

モジュールを利用したアプリケーション構築の例が、Angular の公式から提供されています。
コード例を確認しながら見ていきましょう。

https://angular.jp/generated/live-examples/ngmodules/stackblitz.html

ルーティング NgModule

ルーティング NgModule は、おそらく誰もが目にしたことのある NgModule でしょう。

ルートの設定に特化した NgModule で import と export のセクションで、
RouterModule を処理します。

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

export const routes: Routes = [
    // ...
];

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

ルーティング NgModule では、ルートで利用される Provider などが、
providers のセクションに登録される場面もあります。

ドメイン NgModule

ドメインは NgModule、最も一般的なフィーチャーモジュールです。

アプリケーションの特定の機能にあわせて、コンポーネントやサービス、ルーティングなどが格納されます。

ドメイン NgModule で ルーティングを定義する場合には、
RouterModule.forRoot の代わりに、 RouterModule.forChild を用います。

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

const routes = [
    // ...
];

@NgModule({
  imports: [ RouterModule.forChild(routes) ],
  exports: [ RouterModule ]
})
export class ContactRoutingModule {}

モジュール本体のコードは、以下のような形で構成されます。
import のセクションには ReactiveFormsModule など Module 内で利用するすべての Module を都度定義しなければならない点に注意してください。

import { NgModule } from '@angular/core';

@NgModule({
  imports: [
    // module 内で利用する Module
  ],
  declarations: [ 
    // module 内で利用するコンポーネント  
  ],
  providers:    [ 
    // module 内で完結したライフサイクルを持つ Service   
  ]
})
export class ContactModule { }

このようにして定義した Module は 通常 AppModule に import して利用されます。

ルーテッド NgModule

アプリケーションが大規模になってきた場合、すべての ドメイン NgModule を
AppModule に登録するのは バンドルサイズの増大を招き 通信の観点から非効率が懸念されます。

機能単位で分割された NgModule を URL の第一セグメントに紐づけて遅延ロードさせることによって、
アプリケーションの初期バンドルサイズを削減することが期待できます。

ルーテッド NgModule は こうした遅延ロードされる Module を指すもので、
ルートの AppRoutingModule において以下のような形で import されます。

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

export const routes: Routes = [
  { path: '', redirectTo: 'contact', pathMatch: 'full'},
  { path: 'items', loadChildren: () => import('./items/items.module').then(m => m.ItemsModule) },
  { path: 'customers', loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) }
];

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

重要なことは ルーテッドモジュールを、例えば AppModule などで import してはいけないということです。
ルーテッドモジュールとして意図されたモジュールを、他の module で import した場合、
遅延ロードの効果は失われます。

ルーテッドモジュールの構成は ドメインモジュールと似ていますが、
ルーテッドモジュールは、該当の URL にアクセスするまで決してアクセスされることが無いため、
例えば モジュールの内部に injectedIn:"root" のモジュールなどを含ませていた場合、
アプリケーションの中で発見しづらいバグをもたらす可能性があります。

Shared NgModule

アプリケーションの様々なモジュールから利用されるような機能を詰め込んだモジュールを
Shared NgModule と呼ぶケースがあります。

アプリケーションの中で利用する カスタムパイプやディレクティブなどをまとめたり、
特定の UI を実現するような コンポーネントをまとめたりするのに用いられます。

外部に公開するパイプやディレクティブ、コンポーネントなどの要素は、
一つづつ モジュールの exports セクションに登録する必要があります。

また、アプリケーション内に複数の ドメイン NgModule が存在する場合、
必要に応じてそれぞれの モジュール内で import する必要があるということも覚えておいてください。

参考

https://angular.jp/guide/module-types

Discussion