アングル・アーキテクチャ


前の記事では、コマンドパターンを使用してスケーラブルな方法でコンテキストメニューのアクション定義を格納することについて書きましたActionService 🎉). ショーケースアプリケーションでは、熱心にロードされたダイアログを使用しました.これは、すべてのダイアログモジュールがブラウザにダウンロードされていることを意味します.それを修正しましょう!
この記事を終了した後に簡単にスケーラブルであり、互いに独立しているとアプリの残りの対話の数十を管理する方法を知られているでしょう.
ショーケースアプリケーションのダイアログシーケンスを示すGIF

記事へのリンク


問題


現在、ダイアログの構造は次のようになります.

各ダイアログ:
  • モジュールを持っています.
  • コンポーネントを持っています.
  • データ入力モデル構造を持ちます.
  • これまでのところ良い.角材を用いた対話を示すMatDialog サービス
    constructor(private matDialog: MatDialog) {
       const dialogData: JobUserAssignDialogDataModel = {
             jobId: params.jobId,
       };
    
       this.matDialog
           .open(JobUserAssignDialogComponent, {data: dialogData})
    }
    
    残念ながら、このアプローチには2つの問題があります.

    1 .すべてのダイアログはappmoduleでバンドルされます


    角度がインスタンス化するためにJobUserAssignDialogComponent それはどこかで宣言される必要がありますJobUserAssignDialogModule また、それをインポートする必要がありますAppModule :
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        ...,
        JobUserAssignDialogModule,
        ConfirmationDialogModule
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    
    今、すべてのダイアログがバンドルされてmain.js ファイルは、ユーザーがすべてのそれらを使用していないページ上にある場合でも、それらをすべてダウンロードする結果!

    2 .対話データの静的な型強制


    表情で.open(JobUserAssignDialogComponent, {data: dialogData}) the data フィールドにはany 指定された型を手動で指定するまで.これは非常にエラーがちなのは、非常に簡単に何かをするか、まったく何かを入力するのは簡単です.
    ダイアログを持つダイアログデータインターフェイスに自動的に一致するメソッドが必要です.

    解決策


    前の記事からのアクション定義の場合と同じように、各ダイアログモジュールごとにダイアログサービスをするのは賢明です.
    まず、ダイアログサービスの基本クラスを作成します.
    @Directive()
    // tslint:disable-next-line:directive-class-suffix
    export abstract class AsyncDialog<ComponentType, DataType, ReturnType = unknown> {
      constructor(protected matDialog: MatDialog) {
      }
    
      abstract async open(data: DataType): Promise<MatDialogRef<ComponentType, ReturnType>>;
    }
    
    このクラスはほとんど一貫性を保つためです.
    The AsyncDialog クラスは3つの一般的なパラメータを消費します:
  • ComponentType -ダイアログクラス
  • DataType -ダイアログがそのオープナーから受け取る構造.
  • 返り値-構造体がそのオープナーに戻ります.
  • また、それを注釈する必要があります@Directive() , バージョン10以降では、デコレータなしでクラスのコンストラクタパラメータからすべての型を削除しますMatDialog .
    1つの抽象メソッドもあります.
  • open() - ダイアログを開くためのテンプレート
  • ベースクラスを作成した後にJobUserAssignDialogService :
    @Injectable({providedIn: 'root'})
    export class JobUserAssignDialogService extends AsyncDialog<JobUserAssignDialogComponent, JobUserAssignDialogDataModel, UserModel> {
    
      async open(data: JobUserAssignDialogDataModel): Promise<MatDialogRef<JobUserAssignDialogComponent, UserModel>> {
        // the magical part of importing a module asynchronously
        await import('../job-user-assign-dialog.module');
    
        return this.matDialog.open(JobUserAssignDialogComponent, {data});
      }
    }
    
    The JobUserAssignDialogService から継承するAsyncDialog 以前に作成された基底クラス.3つのジェネリックパラメーターでは、このサービスがどのコンポーネントに対して責任があるか、何が消費されているか、何が返されるかを指定します.
    魔法の部分はawait import('...') . これは、まだダウンロードされていない場合は、モジュールファイルを取得する角度を伝えます.その後、私たちはちょうど前に行ったようにダイアログを開きます.
    注:使用async キーワードは、非同期のインポートが約束を返すので、操作が完了するまでダイアログを開くと待機する必要があります.
    以下の例では、アクションをクリックしたときのみモジュールファイルがフェッチされます.

    予想外の問題


    GIFは、ダイアログモジュールが非同期にフェッチされていることを示しますng build のソースコードを詳しく見てくださいJobListViewComponent 我々は見つけることができますJobUserAssignDialogComponent 代わりに怠惰なモジュールのアプリにバンドルされています.

    何を待つ?
    ダイアログコンポーネントは怠惰に読み込まれてはいけませんか?我々はちょうど怠惰にモジュールをダウンロードしている!何が起こっている!
    ...
    この動作を行う行は、ダイアログを開く行です.でもどうして?
    @Injectable({providedIn: 'root'})
    export class JobUserAssignDialogService extends AsyncDialog<...> {
    
      async open(data: JobUserAssignDialogDataModel): Promise<...> {
        await import('../job-user-assign-dialog.module');
    
        // THIS IS THE CULPRIT!
        return this.matDialog.open(JobUserAssignDialogComponent, {data});
      }
    }
    

    依存グラフ


    インポート連鎖を解析した場合(何をインポートするか)、次のグラフを描画できます.

    ビューのジョブビューでは、アクションを呼び出してダイアログに3つのインポートがあります.
  • JobsComponent 注射するJobAssignAction そして、値としてタイプを使用します(角度はコンストラクタparamsのタイプを保存します、そうすれば、何を注入するかについてわかっています).
  • JobAssignAction 注射するJobUserAssignDialogService また、値として型を使用します.
  • JobUserAssignDialogService オープンJobUserAssignDialogComponent として、その型を値として使用します.
  • だからJobsComponent 角度で走らせるには、JobUserAssignDialogComponent , すべての道に沿ってそれは値として使用された!
    それでは、どのように修正することができますか?

    値対型の使用


    まず、値と型の違いを理解する必要があります.

    値として


    以下の例では、クラスを値として使用します.これはJavaScriptにコンパイルした後にまだ存在します.
    // typescript
    const componentClass = JobUserAssignDialogComponent;
    
    // javascript
    const componentClass = JobUserAssignDialogComponent;
    

    種類として


    以下の例では、クラスを型として使用します.これは、JavaScriptからすべてのインポートを完全に削除することを意味します.
    // typescript
    const component: JobUserAssignDialogComponent = {};
    
    // javascript
    const component = {};
    

    コンポーネントの遅延読み込み


    今、私たちはどういうわけかJobUserAssignDialogComponent 値として、値としてインポートせずに.我々は、我々はlazilyいくつかの助けをインポートインポートすることができます!
    @NgModule({
      declarations: [JobUserAssignDialogComponent],
      imports: [...],
    })
    export class JobUserAssignDialogModule {
      static getComponent(): typeof JobUserAssignDialogComponent {
        return JobUserAssignDialogComponent;
      }
    }
    
    現在、コンポーネントを値としてインポートするモジュールの責任は、静的メソッドをgetComponent() , そして、我々にそれを返してください.
    その後、ダイアログサービスを調整できます.
    @Injectable({providedIn: 'root'})
    export class JobUserAssignDialogService extends AsyncDialog<JobUserAssignDialogComponent,...> {
    
      async open(data: JobUserAssignDialogDataModel): Promise<MatDialogRef<...> {
        const importedModuleFile = await import('../job-user-assign-dialog.module');
    
        // the `importedModuleFile` has all the imports of the file
        // and since one of them is `JobUserAssignDialogModule`
        // we can use it to invoke previously defined `getComponent()` method
        return this.matDialog.open(
          importedModuleFile.JobUserAssignDialogModule.getComponent(),
          {data},
        );
      }
    }
    
    ET Vil il!ダイアログコンポーネントは、現在Lazzilyロードされているモジュールとバンドルされています.
    ジェネリックパラメーターとして使用するコンポーネントクラスについてはAsyncDialog<JobUserAssignDialogComponent,...> ? 幸運にも、それはタイプとして使用され、コンパイルフェーズで完全に削除されます.

    最終プロジェクト構造


    scructureは次のようになります.

    各ダイアログ:
  • 専用のモジュールを持っています.
  • 専用コンポーネントです.
  • 専用のデータ入力構造を持ちます.
  • 専用のasyncサービスを持っています.
  • 我々は今我々のアプリに50以上のダイアログを追加し、それらのすべてを使用して1つのビューのいずれもすぐに来るだろう.それはいくつかのケースでは、同時に2つのMBのライブラリを使用して、バイオリンを再生するときに便利です.結果が結果としてそれをすべてダウンロードする正しい行動を引き起こすまで、ビューはスリムで軽いでしょう.

    Don't forget to remove Dialog Module imports from your AppModule


    github repoはstackblitzと同様に以下のようになります.

    ハンバーグ / 角度における遅延負荷ダイアログ


    遅延負荷角ダイアログのショーケース


    結論


    最後の5分の間に、なぜすべてのダイアログを一度にロードすることが最適ではないことを学んだ.ダイアログがバンドルされている問題を発見したmain.js ファイル.また、クラスを値として使用し、それを型として使用することの違いもわかりました.最後に、すべてのダイアログのソリューションを非同期でモジュールクラスを読み込み、コンポーネントクラスのリファレンスを取得するためのプロキシとして使用することで、実装を行います.
    私は、あなたが私がそれが可能であるとわかったとき、私がそうであったのと同じくらい感銘を受けることを望みます:)
    次の記事では角度について書きます.おそらく.
    周りを見ろ.