はじめに
フロントエンドの機能を実装していくと、ある変数の条件によってページの内容を切り替えたい、ということはよくある。
たとえば以下のHTMLでは、セレクトボックスで指定したselectedLanguage
の値に応じて、switch
文で画面上に表示する文言を切り替えている。
<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>
プルダウンでプログラミング言語を選択すると、そのプログライング言語の説明文が表示されるシンプルなページである。
ライブデモはこちらから参照
サンプルは各コンポーネントに引数が1つしかないシンプルないコンポーネントのため、複雑さは感じない。
しかし機能追加していき、Input
やOutput
が増えていくと以下の課題が発生する
- 複数のコンポーネントでプロパティバインディング(
Input
やOutput
)が重複し、ソースコードが冗長になる - 呼び出し元のソースコードの量が増えて見えづらくなる
具体的には以下の様に、引数が増えてソースコードの見通しが悪くなることが多々ある。
<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に関連し、必要になったタイミングで読み込むので、ロード時間の短縮、効率的なメモリ管理が可能となる。
実装
サンプルのページをDynamic Component Loaderの機能を利用し、シンプルにしていく。
利用したソースコードはGithubからも確認可能
デモはこちらから
環境
- Angular v17.2
1. 表示したいコンポーネントのリストを定義
はじめに①で、画面に呼び出したいコンポーネントをリストとして宣言しておく。
そして②でセレクトボックスで選択したプログラミング言語に応じて、対応するコンポーネントなどを取得する。
@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の機能を利用する。
<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で議論が進められているので、将来的に採用される可能性はある。