noxi雑記

.NET、Angularまわりの小ネタブログ

Angular CDK Overlayで画面の絶対位置を指定してオーバーレイを表示する

久しぶりにブログ書いています。どうにも仕事が忙しいと書く気力が出ません。
今回は Angular CDK Overlay を使用してサクッとオーバーレイを表示してみます。オーバーレイは画面の任意の場所に Component を表示することができます。 Angular Material だと Dialog や Select などに使用されています。
Angular CDK Overlay には Global と Connected の2種類の位置設定があります。前者は画面上の絶対位置でオーバーレイを表示し、後者は特定の要素に対して相対位置で表示します。この記事では前者の Global を扱います。

material.angular.io



環境

Angular CLI: 9.1.3
Node: 12.16.2
OS: win32 x64

Angular: 9.1.3
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.901.3
@angular-devkit/build-angular     0.901.3
@angular-devkit/build-optimizer   0.901.3
@angular-devkit/build-webpack     0.901.3
@angular-devkit/core              9.1.3
@angular-devkit/schematics        9.1.3
@angular/cdk                      9.2.1
@angular/material                 9.2.1
@ngtools/webpack                  9.1.3
@schematics/angular               9.1.3
@schematics/update                0.901.3
rxjs                              6.5.5
typescript                        3.8.3
webpack                           4.42.0

今回のコードは こちら に Push しています。

オーバーレイで Component を表示する

まずは Angular CDK Overlay を使用して単純に Component を表示してみます。ここで表示する Component は OverlayContentComponent とします。

シンプルに表示する

まずは app.module.tsOverlayModule を追加します。意外と忘れがちです。
次に app.component.tsコンストラクターOverlay をインポートし、 showOverlay メソッドを追加してオーバーレイを表示します。ただオーバーレイをただ表示するだけであれば次の3手順だけで表示できます。

  1. Overlay#create(OverlayConfig) でオーバーレイに対する参照を取得する
  2. 表示する Component に対する ComponentPortal を作成する
  3. OverlayRef に ComponentPortal をアタッチする
import type { OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { Overlay } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import type { ComponentRef } from '@angular/core';
import { Component } from '@angular/core';
import { OverlayContentComponent } from './overlay-content/overlay-content.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {

  constructor(
    private readonly _overlay: Overlay,
  ) {
  }

  showOverlay() {
    const config: OverlayConfig = {
      width: 'auto',
      height: 'auto',
    };
    const overlayRef: OverlayRef = this._overlay.create(config);
    const componentPortal: ComponentPortal<OverlayContentComponent> = new ComponentPortal(OverlayContentComponent);
    const componentRef: ComponentRef<OverlayContentComponent> = overlayRef.attach(componentPortal);
  }

}

このコードを実行すると DOM ツリーとは異なる位置に OverlayContent Component が表示されていることが確認出来ます。

f:id:noxi515:20200426135625p:plain
シンプル使用時のDOMツリー

オーバーレイを閉じる

オーバーレイは表示したままだととても邪魔になりますので、表示を消すことも必要です。オーバーレイを削除するには OverlayRef#detach または OverlayRef#dispose を呼び出します。前者は表示されている Component が削除されますが OverlayRef の再利用ができます。後者は OverlayRef 自体の再利用すらできなくなります。あまり OverlayRef を再利用するパターンは無いと思いますので dispose を呼び出しておけば良いです。

<!-- app.component.html -->

<button (click)="showOverlay()">Show overlay</button>
<button (click)="closeOverlay()">Close overlay</button>
// app.component.ts

import type { OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { Overlay } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import type { ComponentRef } from '@angular/core';
import { Component } from '@angular/core';
import { OverlayContentComponent } from './overlay-content/overlay-content.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {

  private _overlayRef?: OverlayRef;

  constructor(
    private readonly _overlay: Overlay,
  ) {
  }

  showOverlay() {
    const config: OverlayConfig = {
      width: 'auto',
      height: 'auto',
    };
    const overlayRef: OverlayRef = this._overlayRef = this._overlay.create(config);
    const componentPortal: ComponentPortal<OverlayContentComponent> = new ComponentPortal(OverlayContentComponent);
    const componentRef: ComponentRef<OverlayContentComponent> = overlayRef.attach(componentPortal);
  }

  closeOverlay() {
    this._overlayRef?.dispose();
    this._overlayRef = undefined;
  }
}

GlobalPositionStrategy で画面の任意の位置に表示する

次に PositionStrategy を使用して、表示するオーバーレイの位置を調整します。 PositionStrategy は Overlay 表示時の位置を決定するためのインターフェースです。 CDK ではこの記事の最初にも述べた通り画面上の絶対位置を指定する GlobalPositionStrategy 、要素に対する相対位置を指定する FlexibleConnectedPositionStrategy の2つが実装されています。もちろんただのインターフェースですので、自分で実装するのも OK です。

この記事では GlobalPositionStrategy を取り扱います。 GlobalPositionStrategy は画面の絶対位置を指定するものなので表示オプションもシンプルで、画面中央に表示する centerVertical/Horizontal や 絶対位置を指定する left/right/top/bottom を使用して位置を決定します。
取得するには Overlay#position#global を呼び出し、例えば画面中央に表示するには以下のように centerVerticalcenterHorizontal をチェインします。

showOverlay() {
  let positionStrategy: GlobalPositionStrategy = this._overlay.position().global();
  positionStrategy = positionStrategy.centerVertically().centerHorizontally();

  const config: OverlayConfig = {
    positionStrategy,

    width: 'auto',
    height: 'auto',
  };
  const overlayRef: OverlayRef = this._overlay.create(config);
  const componentPortal: ComponentPortal<OverlayContentComponent> = new ComponentPortal(OverlayContentComponent);
  const componentRef: ComponentRef<OverlayContentComponent> = overlayRef.attach(componentPortal);
}

f:id:noxi515:20200426142454p:plain
画面中央に表示されたオーバーレイ

left: 10px; bottom: 10px を指定すると左下に表示されます。

f:id:noxi515:20200426145019p:plain
画面左下に表示されたオーバーレイ