Angular CDKのPortalを使ったローディングラッパーの実装
今回はAngular CDK(Component Dev Kit)の Portal 機能を使って、ローディングラッパーコンポーネントを実装する例の紹介です。 Angularの基本的な書き方はわかっている前提の内容になります。
ローディングラッパーとは次のようなテンプレートで、ローディング中はローディング表示を、ローディングが終わったら子要素を表示するようなコンポーネントを指しています。 たとえばこのようなテンプレートです。
<mat-card> <loading-wrapper [loading]="isLoading$ | async"> <div>Done!</div> </loading-wrapper> </mat-card>
このように、ローディング状態によってビューが差し替わります。

CdkPortalの使い方
@angular/cdk/portalからインポートできるPortalModuleによって、cdkPortalOutletなどのいくつかのディレクティブが有効になります。
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { PortalModule } from '@angular/cdk/portal'; import { AppComponent } from './app.component'; import { LoadingWrapperComponent } from './loading-wrapper.component'; @NgModule({ imports: [ BrowserModule, PortalModule ], declarations: [AppComponent, LoadingWrapperComponent], bootstrap: [AppComponent] }) export class AppModule { }
cdkPortalOutletディレクティブは、渡されたCdkPortalに紐づくビューをその位置に表示します。
https://material.angular.io/cdk/portal/api#CdkPortalOutlet
<ng-template [cdkPortalOutlet]="contentPortal"></ng-template>
つまり、ローディングラッパーコンポーネントがおこなうことは、ローディング状態に応じてcontentPortalの中身を差し替えることです。
TemplatePortalの作成
CdkPortalはいくつかの種類がありますが、今回はTemplateRefをビューとして保持するTemplatePortalを使います。
ローディング状態のテンプレートをloadingContent、親コンポーネントから渡されるコンテンツ要素をcontentとして、それぞれViewChildでコンポーネントから参照できるようにします。
<ng-template #loadingContent> <div> <div>Loading...</div> <mat-spinner color="accent"></mat-spinner> </div> </ng-template> <ng-template #content> <ng-content></ng-content> </ng-template> <ng-template [cdkPortalOutlet]="contentPortal"></ng-template>
コンポーネント側では、初期化時と、ローディング状態を制御するloadingプロパティが変わったときにビューをスイッチするようにします。
次のコードにおけるswitchViewメソッドが、TemplateOutletを作成している部分です。
@Component({ selector: 'loading-wrapper', templateUrl: './loading-wrapper.component.html' }) export class LoadingWrapperComponent implements OnInit, OnChanges { @Input() loading: boolean; @ViewChild('loadingContent') loadingContentTemplate: TemplateRef<any>; @ViewChild('content') contentTemplate: TemplateRef<any>; contentPortal: CdkPortal; constructor(private vcRef: ViewContainerRef) { } ngOnInit() { this.switchView(); } ngOnChanges(changes: SimpleChanges) { if (changes.hasOwnProperty('loading')) { this.switchView(); } } // 現在のローディング状態から適切なTemplatePortalを作成する switchView() { this.contentPortal = new TemplatePortal(this.getTemplate(), this.vcRef); } private getTemplate() { if (this.loading) { return this.loadingContentTemplate; } return this.contentTemplate; } }
まとめ
- CdkPortalを使って、状態に応じたビューの差し替えの実装が簡単にできる
- TemplatePortalを使って、- ng-templateから取り出した- TemplateRefを- CdkPortalに変換できる
完成形がこちらです。