🍰

ngx-sweetalert2でCheckboxの状態によってConfirmButtonのdisabledを制御したい

2021/07/11に公開

はじめに

今回は ngx-sweetalert2のCheckbox付きダイアログにおいて、Checkboxがチェックの状態によってConfirmButtonの活性非活性を制御する方法を共有していく。

実装の背景

今回は Serviceクラスのみで実現できる 実装方法にしている。
独自の Swal コンポーネントを実装することでタイトルのことを実現できかつデザイン等も柔軟にできるが、それをするとこのダイアログを使用するPage Component側でこのコンポーネントと @ViewChild() を定義しないといけなくなるため、利用者側としても不便だ。
おそらく、確認ダイアログであれば画面ごとにデザインが変わるといったことは少ないと思うため、このやり方にした。

実装

まずはコードから。以下のように実装した。

dialog.service.ts
import { Injectable } from '@angular/core';
import Swal, { SweetAlertIcon, SweetAlertOptions } from 'sweetalert2';
import { fromEvent, Subscription } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class DialogService {

    constructor() {}

    /**
     * Checkbox付き確認ダイアログを表示する。
     * @param {string} message
     */
    public async openConfirm(message: string): Promise<boolean> {
        const confirmAlert = this._getInstance({
            title: '確認',
            html: message,
            showCancelButton: true,
            input: 'checkbox',
            inputPlaceholder: '確認しました',
        }, 'info');

        let subscription: Subscription;
        const { isConfirmed } = await confirmAlert.fire({
            didOpen() {
                const checkbox = confirmAlert.getInput();
                const confirmButton = confirmAlert.getConfirmButton();
                if (checkbox == null || confirmButton == null) {
                    return;
                }

                confirmButton.disabled = true;
                subscription = fromEvent(checkbox, 'change')
                    .subscribe(({ target }) => {
                        const { checked } = (target as HTMLInputElement);
                        confirmButton.disabled = !checked;
                    });
            },
            didDestroy() {
                if (subscription == null) {
                    return;
                }
                subscription.unsubscribe();
            }
        });

        return isConfirmed;
    }

    /**
     * SweetAlertインスタンスを取得する。
     * @param {SweetAlertOptions} options
     * @param {SweetAlertIcon} icon
     * @return {typeof Swal}
     * @private
     */
    private _getInstance(options: SweetAlertOptions, icon: SweetAlertIcon): typeof Swal {
        return Swal.mixin({
            ...options,
            reverseButtons: true,
            icon
        });
    }
}

大事な部分はこの部分。

let subscription: Subscription;
const { isConfirmed } = await confirmAlert.fire({
    didOpen() {
        const checkbox = confirmAlert.getInput();
        const confirmButton = confirmAlert.getConfirmButton();
        if (checkbox == null || confirmButton == null) {
            return;
        }

                // 初期表示時はCheckboxにチェックがないためdisabledにしておく。
        confirmButton.disabled = true;
        subscription = fromEvent(checkbox, 'change')
            .subscribe(({ target }) => {
                const { checked } = (target as HTMLInputElement);
                confirmButton.disabled = !checked;
            });
    },
    didDestroy() {
        if (subscription == null) {
            return;
        }
        subscription.unsubscribe();
    }
});

ダイアログ中のInput要素およびConfirmButtonの要素はそれぞれ、 Swal.getInput() , Swal.getConfirmButton で取得できる。
あとはSweetAlertで提供されている didOpen() のタイミングで、CheckboxおよびConfirmButtonの要素を取得しRxJSの fromEvent() でCheckboxの変更を検知、その状態によってConfirmButtonのdisabledを制御するといった流れだ。

これによって利用者側はServiceクラスのメソッドを呼び出すだけで簡単にタイトルのことが実現できる。

app.component.ts
import { Component } from '@angular/core';
import { DialogService } from './dialog.service';

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

    public async openConfirm() {
        const isConfirmed = await this._dialog.openConfirm('削除します。よろしいですか?');
        console.log(isConfirmed ? '確認した' : 'キャンセルされた');
    }
}

さいごに

SweetAlertはデザインが良いのとオプションが多いので好き。
あとRxJSは神。

Discussion