2020年10月29日木曜日

Angular・【Simple Modal Module】モーダルを手軽に実装する。


こんにちは、オフィス狛 技術部のpinoです。

今回は、AngularのSimple Modal Moduleの使い方についてご紹介したいと思います。

Simple Modal Moduleは、とても簡単にモーダルが実装できるプラグインです。
bootstrapなどでモーダルを作成されていた方も、この機会にぜひ目を通していただきたい内容です。

作れるもの

Simple Modal Moduleで実装できるモーダルは、以下のデモで試せます。
https://ngx-simple-modal-demo.stackblitz.io/

準備

まずは、以下のコマンドでインストールします。
npm install ngx-simple-modal

基本となるCSSはすでに用意されていますので、angular.jsonに以下を定義します。
angular.json
"styles": [
   "styles.css",
   "../node_modules/ngx-simple-modal/styles/simple-modal.css"
],
これで、モーダル表示時のスタイルやアニメーションはバッチリです。

HTMLは、上記のCSSに定義されているスタイルを使用するような形でテンプレートがある程度決まっていますので、以下の形から派生させていくことになります。
HTML
<div class="modal-content">
    <div class="modal-header">
      <!-- モーダルのタイトル -->
    </div>
    <div class="modal-body">
      <!-- モーダルの本文 -->
    </div>
    <div class="modal-footer">
      <!--
        フッターはbutton要素を置く
        ex.: <button (click)="close()">Cancel</button>
      -->
    </div>
</div>

1. AppModuleにImport

Simple Modal Moduleをimportします。
app.module.ts
import { NgModule} from '@angular/core';
import { CommonModule } from "@angular/common";
import { BrowserModule } from '@angular/platform-browser';
import { SimpleModalModule } from 'ngx-simple-modal'; // 追加🍨
import { AppComponent } from './app.component';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    CommonModule,
    BrowserModule,
    SimpleModalModule // 追加🍨
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

また、冒頭のデモでは、モーダル以外をクリックしたり、escキーを押すことでモーダルを閉じたりできたかと思います。
そのようにオプションを変更したい場合は、以下のようにも定義できます。

imports: [
    ...
    SimpleModalModule.forRoot({container: 'modal-container'}, {...defaultSimpleModalOptions, ...{
      closeOnEscape: true,
      closeOnClickOutside: true,
      wrapperDefaultClasses: 'o-modal o-modal--fade',
      wrapperClass: 'o-modal--fade-in',
      animationDuration: 300,
      autoFocus: true
    }})
  ]

・項目名 ... 説明(型, デフォルト値)
・closeOnEscape ... escapeキー押下でモーダルを閉じる(boolean, false)
・closeOnClickOutside ... モーダル以外をクリックしてモーダルを閉じる(boolean, false)
・bodyClass ... モーダルが開いている間、bodyタグに付与するクラス(string, 'modal-open')
・wrapperDefaultClasses ... <simple-modal-wrapper>に付与するクラス(string, 'modal fade-anim')
・wrapperClass ... モーダルが開いている間、<simple-modal-wrapper>に付与するクラス(string, 'in')
・animationDuration ... モーダルの開閉にかける時間(number, 300)
・autoFocus ... モーダルを閉じた後に、フォーカスをモーダルが開く直前の状態に戻す(boolean, false)

* <simple-modal-wrapper>に関して
デフォルトのラッパーとして、モーダルはこのタグの配下に配置されます。

* wrapperDefaultClasses、wrapperClassに関して
前述で紹介した基本のCSSではデフォルト値が使用される前提でスタイリングされているので、通常はあまり変更する必要がないと思います。
以降で紹介するaddModal(), close()といったモーダルの仕組みだけ利用し、スタイリングは1から行いたい場合などに活用できそうです。

2. モーダルコンポーネントを作成

モーダルのコンポーネントを作成していきます。
HTML
<div class="modal-content">
    <div class="modal-header">
      <h4>{{title || 'Confirm'}}</h4>
    </div>
    <div class="modal-body">
      <p>{{message || 'Are you sure?'}}</p>
    </div>
    <div class="modal-footer">
      <button type="button" class="btn btn-outline-danger" (click)="close()">Cancel</button>
      <button type="button" class="btn btn-primary" (click)="confirm()">OK</button>
    </div>
</div>
TS
import { Component } from '@angular/core';
import { SimpleModalComponent } from "ngx-simple-modal";
export interface ConfirmModel {
  title:string;
  message:string;
}

@Component({
    selector: 'confirm',
    template: './confirm.component.html'
})
export class ConfirmComponent extends SimpleModalComponent<ConfirmModel, boolean> implements ConfirmModel {
  title: string;
  message: string;

  constructor() {
    super();
  }

  confirm() {
    this.result = true; //🍀
    this.close();
  }
}

ポイントは🍀マークのthis.result = true;です。
理由は後述します。

また、close()でモーダルを閉じることができます。こちらはモーダルコンポーネント自らで呼び出すことができるため、TS上だけでなくHTML上でclickイベントに直接割り当てるのもOKです。

3. 作成したコンポーネントをモジュールに登録

App.module.tsに作成したコンポーネントをimportします。

import { NgModule } from '@angular/core';
import { CommonModule } from "@angular/common";
import { BrowserModule } from '@angular/platform-browser';
import { SimpleModalModule } from 'ngx-simple-modal';
import { ConfirmComponent } from './confirm.component'; // 追加🍨
import { AppComponent } from './app.component';
@NgModule({
  declarations: [
    AppComponent,
    ConfirmComponent  // 追加🍨
  ],
  imports: [
    CommonModule,
    BrowserModule,
    SimpleModalModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

* 公式ではentryComponentsに定義するようになっていますが、ver.9から非推奨となっているようです。
弊社ではdeclarationsに定義するだけで問題なく使用しています。
参考: https://dev.to/angular-jp/entrycomponents-53mo

4. 実際に使ってみる

モーダルを表示させたいコンポーネントに組み込みます。
例なのでapp.component.tsに組み込んでみます。

import { Component } from '@angular/core';
import { ConfirmComponent } from './confirm.component';
import { SimpleModalService } from "ngx-simple-modal";

@Component({
  selector: 'app',
  template: './app.component.ts'
})
export class AppComponent {
    constructor(private simpleModalService:SimpleModalService) {}

    showConfirm() {
      this.simpleModalService.addModal(ConfirmComponent, { // 🍀①
        title: 'Confirm title',
        message: 'Confirm message'
      })
      .subscribe(isConfirmed =>{ // 🍀②
        if(isConfirmed) {
          alert('accepted');
        } else {
          alert('declined');
        }
      });
    }
}

🍀①のaddModal()でモーダルを表示させることができます。
モーダルを使用する側でモーダルを閉じたい場合は、removeModal()removeAll()が使用できます。

また、ポイントは🍀②部分です。
「2. モーダルコンポーネントを作成」 でresultにセットした値はここで取得することができます。
resultにはbool値以外も使用することができますので、
Submitされたか」「"はい"が押されたか」「処理に成功したか」など
様々なフラグをカスタマイズするのもいいですね。

注意点

これは実際に私がハマったことなのですが、いくらsubscribeしているからといってresultの変更を常々検知できるわけではありません。
ngOnDestroy時にObserverbleとしてresultが返される仕組みだそうなので、resultを取りたい場合はモーダルを閉じる必要があります。

今回記事を書くにあたり公式を確認したところ、その旨記載がありました。
ハマったとき、公式をちゃんと確認する癖をつけなければ...!


補足

公式にはBootstrapと一緒に使う方法も載っていますので、「Bootstrapと完全に縁を切るのは難しいけど、モーダルをコンポーネントとして管理したいんだよなぁ...。」といった場合にもおすすめだと思いました。
https://www.npmjs.com/package/ngx-simple-modal#what-if-i-want-to-use-bootstrap-3-or-4


今回は公式の内容を参考に、Simple Modal Moduleについて紹介しました。
参考になればうれしいです。

0 件のコメント:

コメントを投稿