ご注意 perev。:この記事を理解するには、Angularの基本的な知識(コンポーネントとは何か、単純なSPAアプリケーションの作成方法など)が必要です。 このトピックに精通していない場合は、最初にSPAアプリケーションを作成する例に慣れることをお勧めします。 ドキュメント。
1年前、私はすでにNgModulesに関する記事を公開しました 。これは、モジュール、名前空間などをいつインポートするかの技術的な詳細を説明しています。 レビューに推奨(perevに注意してください。コンテンツに関する記事は、冒頭で参照した記事と類似しています) 。
私は最近、Angularが私に与えた挑戦を受け入れました。 これまで、Angularの公式ドキュメントで提案されているアプローチを使用してきました。 しかし、大きなプロジェクトに達すると、欠陥が現れ始めました。
私はNgModulesのマニュアルを詳細に勉強し始めました。これはFAQで12ページの詳細な説明に成長しました。 しかし、質問を注意深く読んだ後、答え以上のものがありました。 たとえば、サービスを実装する方がよいのはどこですか? この質問に対する明確な答えはありませんでした。 さらに、いくつかの決定は、マニュアルの文脈で互いに矛盾しています。
NgModules
全体を消化した後、以下に基づいて、Angularアプリケーションアーキテクチャソリューションを実装することにしました。
- 構造 :小規模なアプリケーションでは簡単、大規模なプロジェクトではスケーラビリティ。
- ユーザビリティ :他のプロジェクトでソリューションを使用する能力。
- 最適化 (遅延ロードを含む);
- テスト容易性 。
角度モジュール
角度モジュールとは何ですか?
実際、モジュールの主な目標は、コンポーネントや相互に関連するサービスをグループ化することです。 そして、一般的に、これ以上何もありません。 たとえば、メインページにニュースブロックがあるとします。 おおまかに言うと、視覚部分はコンポーネントであり、データベースからデータを取得するメカニズムはサービスです。
Javaに精通している人にとっては、Angularモジュールはパッケージであり、C#/ PHPでは名前空間です。
残っている質問は1つだけです-アプリケーションの機能を正しくグループ化する方法は?
角度モジュールの種類
それらの3つだけがあります。
- ページモジュール
- サービスモジュール ;
- 再利用可能なコンポーネントモジュール 。
ng new projectname
開始アプリケーションを作成したら
その後、少なくともページモジュールを作成しました。 この場合、1つがメインです。
アプリケーションが成長するにつれて、ページ、サービス、コンポーネント用の新しいモジュールを作成し、それらをグループ化します。 もちろん、サービスを提供するスケーラブルなアプリケーションを取得し、すべての機能を1つのファイルにマージしたくない場合を除きます。
ページモジュール
ページモジュールは、アプリケーションの領域を論理的に分離するようにルーティングおよび設計されています。 ページモジュールは、メインモジュール(通常はAppModule
と呼ばれAppModule
)に一度読み込まれるか、遅延読み込みによって読み込まれます。
たとえば、認証、ログアウト、および登録ページでは、 AccountLogin
モジュールが必要です。 ヒーローリストページ、ヒーローページなどのHeroesModule
(perevに注意してください。ここは、公式ドキュメントに記載されているトレーニングプロジェクトを意味します) 。
ページモジュールには以下を含めることができます。
- / shared :サービスとインターフェース。
- /ページ :ルートを持つコンポーネント。
- / components :データの視覚化のためのコンポーネント。
ページの公共サービス
ページにデータを表示するには、まずどこかからこのデータを取得する必要があります。 これがサービスが必要なものです。
@Injectable() export class SomeService { constructor(protected http: HttpClient) {} getData() { return this.http.get<SomeData>('/path/to/api'); } }
その後、一部のページには同様のデータが必要になります。これは、同じタイプのサービスを意味します。 この場合、特定のモジュールではなく、アプリケーション全体で1つのサービスを公開する必要があります。
しかし、ベストプラクティスとしては、特定のページに特定の種類のデータ、特定のサービスが必要になるようにモジュールを設計することをお勧めします。 この場合、このサービスをカプセル化し、アプリケーション全体ではなく1つのモジュール内でのアクセスを制限する必要があります。
ご注意 翻訳。
このアーキテクチャを使用すると、アプリケーションのメンテナンスが簡単になります。 すべてのアプリケーションロジックはブロックに分割され、特定の機能の実行を担当します。 すべてを1つのサービスにマージし、アプリケーション全体で利用できるようにすると、機能の拡張に問題が発生し、インターフェイス、単一の責任、およびその他のSOLIDの分離の原則と矛盾することになります。 ただし、アプリケーションのアーキテクチャを設計する方法はユーザー次第です。
例として以前に発表されたAccountManager
モジュールに戻りましょう。 このモジュールAccountService
のサービスは「thin」であり、必要に応じて、ユーザーのロールモデルに応じて「yes」または「no」と回答する必要があります。 このサービスでは、ユーザーステータス(オンラインかどうか)を実装できません。 このモジュールは、アプリケーションの一部では必要ない場合があります。 したがって、ユーザーステータスは、アプリケーション全体で利用できるグローバルサービスに移動する必要があります(以下を参照)。
ページモジュール:ルーティング
ページコンポーネントは、サービスによって取得されたデータベースからの情報を表示する役割を果たします。
コンポーネントでデータを直接表示できますが、これを行う必要はありません。 データを変数として別のコンポーネントに渡すことができます
@Component({ template: `<app-presentation *ngIf="data" [data]="data"></app-presentation>` }) export class PageComponent { data: SomeData; constructor(protected someService: SomeService) {} ngOnInit() { this.someService.getData().subscribe((data) => { this.data = data; }); } }
各コンポーネントには独自のルートがあります。
データ可視化コンポーネント
データ表示コンポーネントは、 @ Inputデコレータを使用して情報を抽出し、テンプレートに表示します
@Component({ selector: 'app-presentation', template: `<h1>{{data.title}}</h1>` }) export class PresentationComponent { @Input() data: SomeData; }
MVxですか?
model-controller-representationパターンに精通している人は、質問をするでしょう-それですか? 理論に従えば、いいえ。 ただし、MVxを使用してAngularアーキテクチャを想像する方が簡単な場合は、次のようにします。
サービスはModelsに匹敵します
プレゼンテーションコンポーネントはViewに似ています
ページコンポーネントは、 Controllers \ Presenters \ ViewModels (使用するものを選択)になります。
これは正確にMVxではない(またはまったくMVxではない)にもかかわらず、このアプローチの目標は同じです。問題を解決する責任を共有します。 なぜこれが重要なのですか? その理由は次のとおりです。
- 「薄い」コンポーネント(プレゼンテーション)は他のプロジェクトで使用できますが、
- コンポーネント発見戦略の最適化、
- 「薄い」コンポーネントのテスト可能性(アプリケーションロジックを共有しない場合、テストを忘れると、地獄になります)。
まとめ
ページモジュールの例
@NgModule({ imports: [CommonModule, MatCardModule, PagesRoutingModule], declarations: [PageComponent, PresentationComponent], providers: [SomeService] }) export class PagesModule {}
サービスはこのモジュールにカプセル化されます。
グローバルサービスモジュール
グローバルサービスモジュールは、アプリケーション内のどこからでもサービスへのアクセスを提供します。 このようなサービスにはグローバルスコープがあるため、これらのモジュールはルートモジュール( AppModule
)に一度だけロードされ、どこでも使用可能です。 遅延ロードを実装する場合。
このようなサービスを少なくとも1つは間違いなく使用しています。 例: HttpModule 。 しかし、すぐにHttpModule
に似たサービスが必要になります。 たとえば、 AuthModule
は、ユーザーとそのトークンの現在のステータスを保存し、アプリケーション全体、ユーザーセッション全体で必要です。
使いやすさ
グローバルサービスのモジュールの設計に注意を払っている場合は、特定のアプリケーションを実装するのではなく、視覚的な部分なしでそれを行い、サービスロジックを個別のモジュールに分割し、インターフェイスレベルで設計します(つまり、特定のアプリケーションの依存関係を実装しません)モジュールは他のプロジェクトで使用できます。
モジュールを他のプロジェクトで(つまり、外部から)使用できるようにする場合は、NgModule、インターフェイス、および実装用のトークンをエクスポートするエントリポイントを作成する必要があることに注意してください。
export { SomeService } from './some.service'; export { SomeModule } from './some.module';
CoreModuleを行う必要があります
必要ありません。 公式ドキュメントでは、すべてのグローバルサービスをCoreModuleに実装することを提案しています。 もちろん、それらを/ core / modulesにグループ化できますが、責任の分割に注意を払い、すべてを1つのCoreModule
「マージ」しないでCoreModule
。 そうしないと、実装された機能を他のプロジェクトで使用できなくなります。
合計で
サービスのグローバルモジュールの例
@NgModule({ providers: [SomeService] }) export class SomeModule {}
UIコンポーネントとデータの受信方法
UIコンポーネント(ウィジェットなど)は「薄く」、「ページモジュール」で説明したように、受信したデータを視覚化する役割のみを果たします。 コンポーネントは、 @ Inputデコレータを使用してデータを受信します(<ng-content>から、場合によっては他のソリューションから)。
Component({ selector: 'ui-carousel' }) export class CarouselComponent { @Input() delay = 5000; }
サービスだけに頼るべきではありません。 なんで? サービスにはオファーに応じて独自の特性があるためです。 たとえば、API URLが変更される場合があります。 データ表示は、モジュールのページ内のコンポーネントの問題です。 UIコンポーネントは、誰かが提供したデータを受け取りますが、それらは受け取りません。
パブリックおよびプライベートコンポーネント
コンポーネントを使用可能(パブリック)にするには、モジュールでエクスポートする必要があります。 ただし、すべてをインポートする必要はありません。 ネストされたコンポーネントは、アプリケーションの他の場所で必要とされない場合、プライベートのままにする必要があります。
ディレクティブとパイプ
ディレクティブとパイプのモジュールについて話す場合、UIコンポーネントと似ています。 必要に応じて、モジュールでエクスポートし、必要な場所で使用します。
非表示(プライベート)サービス
コンポーネントUI内でのみデータを操作するには、 NgModule
ではなく、コンポーネント内でのみサービスを実装し、コンポーネント以外のすべてに対してサービスを閉じることができます。 この場合、次のようになります
@Component({ selector: 'some-ui', providers: [LocalService] }) export class SomeUiComponent {}
公共サービス
UIコンポーネントに実装されているサービスへのアクセスを開きたい状況を想像してください。 これはできる限り避けるべきですが、可能です。
NgModule
でサービスへのアクセスを開くと、モジュールの複数のロードの問題が発生します。 モジュールにコンポーネントを実装します。
この問題を解決するには、この方法でモジュールを実装する必要があります
xport function SOME_SERVICE_FACTORY(parentService: SomeService) { return parentService || new SomeService(); } @NgModule({ providers: [{ provide: SomeService, deps: [[new Optional(), new SkipSelf(), SomeService]], useFactory: SOME_SERVICE_FACTORY }] }) export class UiModule {}
ちなみに、これはAngular CDKに実装されています(少なくともそうでした)。
使いやすさ
モジュールの形でUIコンポーネントを使用するには、コンポーネント\パイプ\ディレクティブなどをエクスポートし、アクセスポイントを作成してそれらへのアクセスを開く必要があります
export { SomeUiComponent } from './some-ui/some-ui.component'; export { UiModule } from './ui.module';
SharedModuleを実行する必要がありますか?
SharedModuleのすべてのユーザーインターフェイス(UIコンポーネント)をすべてマージする必要がありますか? ドキュメントではこのソリューションを提供していますが、 SharedModule
実装された各モジュールは、インターフェイスではなくプロジェクトレベルで実装されます。
特にVS Code(または他のIDE)でこのプロセスを自動化することにより、プロジェクトの作成時に依存関係をインポートする際に問題はありません。
ただし、たとえば、ユーザーインターフェイスエンティティごとに個別のモジュールを作成し、/ uiフォルダーに配置することをお勧めします。
合計で
UIモジュールの例
@NgModule({ imports: [CommonModule], declarations: [PublicComponent, PrivateComponent], exports: [PublicComponent] }) export class UiModule {}
結果は何ですか?
上記を考慮してアプリケーションを設計する場合:
小規模アプリケーションでも大規模アプリケーションでも、遅延負荷の有無に関係なく、適切に構造化されたアーキテクチャを使用できます。
グローバルモジュールまたはUIコンポーネントをライブラリにパッケージ化し、他のプロジェクトで使用できます。
苦労せずにアプリケーションをテストします。
プロジェクト構造の例
app/ |- app.module.ts |- app-routing.module.ts |- core/ |- auth/ |- auth.module.ts |- auth.service.ts |- index.ts |- othermoduleofglobalservice/ |- ui/ |- carousel/ |- carousel.module.ts |- index.ts |- carousel/ |- carousel.component.ts |- carousel.component.css |- othermoduleofreusablecomponents/ |- heroes/ |- heroes.module.ts |- heroes-routing.module.ts |- shared/ |- heroes.service.ts |- hero.ts |- pages/ |- heroes/ |- heroes.component.ts |- heroes.component.css |- hero/ |- hero.component.ts |- hero.component.css |- components/ |- heroes-list/ |- heroes-list.component.ts |- heroes-list.component.css |- hero-details/ |- hero-details.component.ts |- hero-details.component.css |- othermoduleofpages/
このアーキテクチャについてコメントがある場合は、コメントを残してください。
-ロシア語を話すAngularコミュニティの電報 。