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
に変換できる
完成形がこちらです。