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