LoginSignup
1
0

[Angular]複数のcomponentの表示の切り替えをDynamic Component Loaderでシンプルにする

Last updated at Posted at 2024-02-18

はじめに

フロントエンドの機能を実装していくと、ある変数の条件によってページの内容を切り替えたい、ということはよくある。

たとえば以下のHTMLでは、セレクトボックスで指定したselectedLanguageの値に応じて、switch文で画面上に表示する文言を切り替えている。

src/app/app.component.html
<div>
  <h1>Programming Language</h1>

  <!-- 表示するページをセレクトボックスから選択する -->
  <select [(ngModel)]="selectedLanguage">
    <option [value]="programmingLanguageEnum.JAVA">Java</option>
    <option [value]="programmingLanguageEnum.JAVASCRIPT">JavaScript</option>
    <option [value]="programmingLanguageEnum.TYPESCRIPT">TypeScript</option>
  </select>

  <!-- セレクトボックスで選択した内容で表示するページを切り替える -->
  <div>
    @switch (selectedLanguage) {
      @case (programmingLanguageEnum.JAVA) {
        <app-java-page
            [input]="Javaのページ"
        ></app-java-page>
      }
      @case (programmingLanguageEnum.JAVASCRIPT) {
          <app-javascript-page
            [input]="JavaScriptのページ"
          ></app-javascript-page>
      }
      @default {
        <app-typescript-page
            [input]="TypeScriptのページ"
        ></app-typescript-page>
      }
    }
  </div>
</div>

プルダウンでプログラミング言語を選択すると、そのプログライング言語の説明文が表示されるシンプルなページである。

スクリーンショット 2024-02-18 18.07.34.png

ライブデモはこちらから参照

サンプルは各コンポーネントに引数が1つしかないシンプルないコンポーネントのため、複雑さは感じない。
しかし機能追加していき、InputOutputが増えていくと以下の課題が発生する

  • 複数のコンポーネントでプロパティバインディング(InputOutput)が重複し、ソースコードが冗長になる
  • 呼び出し元のソースコードの量が増えて見えづらくなる

具体的には以下の様に、引数が増えてソースコードの見通しが悪くなることが多々ある。

src/app/app.component.html
<div>
  <h1>Programming Language</h1>

  <!-- 表示するページをセレクトボックスから選択する -->
  <select [(ngModel)]="selectedLanguage">
    <option [value]="programmingLanguageEnum.JAVA">Java</option>
    <option [value]="programmingLanguageEnum.JAVASCRIPT">JavaScript</option>
    <option [value]="programmingLanguageEnum.TYPESCRIPT">TypeScript</option>
  </select>

  <!-- セレクトボックスで選択した内容で表示するページを切り替える -->
  <div>
    @switch (selectedLanguage) {
      @case (programmingLanguageEnum.JAVA) {
        <app-java-page
            [arg1]="arg1"
            [arg2]="arg2"
            [arg3]="arg3"
            (func1)="func1"
            (func2)="func2"
            (func3)="func3"
        ></app-java-page>
      }
      @case (programmingLanguageEnum.JAVASCRIPT) {
          <app-javascript-page
            [arg1]="arg1"
            [arg2]="arg2"
            [arg3]="arg3"
            (func1)="func1"
            (func2)="func2"
            (func3)="func3"
          ></app-javascript-page>
      }
      @default {
        <app-typescript-page
            [arg1]="arg1"
            [arg2]="arg2"
            [arg3]="arg3"
            (func1)="func1"
            (func2)="func2"
            (func3)="func3"
        ></app-typescript-page>
      }
    }
  </div>
</div>

この課題に対して、Angularの動的コンポーネントローダーの機能を利用し解決する。

Dynamic Component Loader(動的コンポーネントローダー)

Dynamic Component Loaderは、画面表示に利用するコンポーネントを一度に全て読み込むのではなく、必要に応じて読み込む仕組みである。
ReactのLazy Loadと似た仕組みである。

Dynamic Component Loadeを利用して得られるメリットとしては、以下が挙げられる

  1. 条件付きレンダリング
    特定の条件でのみ表示するコンポーネントを、必要になったタイミングっで読み込むことができる

  2. パフォーマンス改善
    1に関連し、必要になったタイミングで読み込むので、ロード時間の短縮、効率的なメモリ管理が可能となる。

実装

サンプルのページをDynamic Component Loaderの機能を利用し、シンプルにしていく。

利用したソースコードはGithubからも確認可能

デモはこちらから

環境

  • Angular v17.2

1. 表示したいコンポーネントのリストを定義

はじめに①で、画面に呼び出したいコンポーネントをリストとして宣言しておく。
そして②でセレクトボックスで選択したプログラミング言語に応じて、対応するコンポーネントなどを取得する。

src/app/app.component.ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    RouterOutlet,
    JavaPageComponent,
    JavascriptPageComponent,
    TypescriptPageComponent,
  ],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css',
})
export class AppComponent {
  title = 'component-di-sample';
  selectedLanguage = 'Java';

  programmingLanguageEnum = ProgrammingLanguage;

  // ①表示したいコンポーネントのリスト
  private programmingLanguageList = [
    {
      component: JavaPageComponent,
      key: ProgrammingLanguage.JAVA,
      inputs: {
        input: 'Javaのページ',
      },
    },
    {
      component: JavascriptPageComponent,
      key: ProgrammingLanguage.JAVASCRIPT,
      inputs: {
        input: 'JavaScriptのページ',
      },
    },
    {
      component: TypescriptPageComponent,
      key: ProgrammingLanguage.TYPESCRIPT,
      inputs: {
        input: 'TypeScriptのページ',
      },
    },
  ] as {
    component: Type<any>;
    key: ProgrammingLanguage;
    inputs: Record<string, unknown>;
  }[];

  // ②セレクトボックスで選択された言語のコンポーネントを取得
  get currentProgrammingLanguage() {
    return this.programmingLanguageList.find(
      (language) => language.key === this.selectedLanguage
    )!;
  }
}

2. Dynamic Component Loaderの設定

ngComponentOutletディレクティブを利用し、Dynamic Component Loaderの機能を利用する。

src/app/app.component.html
<div>
  <h1>Programming Language</h1>

  <!-- 表示するページをセレクトボックスから選択する -->
  <select [(ngModel)]="selectedLanguage">
    <option [value]="programmingLanguageEnum.JAVA">Java</option>
    <option [value]="programmingLanguageEnum.JAVASCRIPT">JavaScript</option>
    <option [value]="programmingLanguageEnum.TYPESCRIPT">TypeScript</option>
  </select>

  <!-- セレクトボックスで選択した内容で表示するページを切り替える -->
  <div>
    <ng-container *ngComponentOutlet="
    currentProgrammingLanguage.component;
    inputs: currentProgrammingLanguage.inputs;
  "
  />
  </div>
</div>

@Inputで渡している値はsrc/app/app.component.tsの①でコンポーネントを宣言する際に、inputsにkey-value形式で宣言する。

その他ngComponentOutletで動的コンポーネント設定可能な値は、公式ドキュメントを参照。

注意: 動的コンポーネントに@OutputのEventを渡すことができない

2024年2月18日現時点で、動的コンポーネントで表示したコンポーネントに、@OutputのEventを渡すことができない

こちらに関しては以下のISSUEで議論が進められているので、将来的に採用される可能性はある。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0