Как стать автором
Обновить

Заворачиваем в Promise модальное окно подтверждения действия

Время на прочтение3 мин
Количество просмотров4.5K
Когда пользователь совершает какие-то критические и/или необратимые действия, перед тем, как отправить запрос на сервер, нужно запросить у пользователя подтверждение.

Как правило, выводится модал «Вы уверены, что хотите сделать то то и то то» и внизу две кнопки: Да и Нет. Если пользователь нажал «да», то отправляем запрос на сервер и закрываем модал. Если «нет», просто закрываем модал.

Это стандартный функционал, который обычно используется в нескольких местах в проекте. Также при наращивании функционала проекта, скорее всего добавиться еще несколько мест, где нужны модалы с подтверждением. Поэтому, во избежание дублирования кода, однозначно такой модал нужно выносить в отдельный компонент. Во избежание соблазна забивать костыли, этот компонент должен быть максимально универсальным и простым в использовании.

Перейдем от лирики к делу. Для отображения модала будем использовать Bootstrap.

Собственно мой вариант такого компонента:

yes-no-modal.component.html
<div bsModal #yesNoModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-{{type}}" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title">{{title}}</h4>
        <button type="button" class="close" (click)="onNoClick()" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <p>{{body}}</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" (click)="onNoClick()">{{noBtnText}}</button>
        <button type="button" class="btn btn-{{type}}" (click)="onYesClick()">{{yesBtnText}}</button>
      </div>
    </div><!-- /.modal-content -->
  </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

yes-no-modal.component.ts
import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {ModalDirective} from 'ngx-bootstrap/modal';

@Component({
  selector: 'app-yes-no-modal',
  templateUrl: './yes-no-modal.component.html',
  styleUrls: ['./yes-no-modal.component.css']
})
export class YesNoModalComponent implements OnInit {

  @ViewChild('yesNoModal') public yesNoModal: ModalDirective;

  @Input() private type = 'info';
  @Input() private title = '';
  @Input() private body = '';
  @Input() private yesBtnText = 'Да';
  @Input() private noBtnText = 'Нет';

  constructor() { }

  ngOnInit(): void {
  }

  public showAsync(data = null): Promise<any> {
    return new Promise<any>((resolve, reject) => {
      this.yesNoModal.show();
      this.onYesClick = () => {
        this.yesNoModal.hide();
        resolve(data);
      };
      this.onNoClick = () => {
        this.yesNoModal.hide();
        reject(data);
      };
    });
  }

  private onYesClick(): any {}
  private onNoClick(): any {}

}

В параметры вынес заголовок предупреждения, текст предупреждения, цветовую схему/уровень важности (danger, info, warning), также можно переопределить надпись на кнопках.

Для показа модала вызываем showAsync, в который можем передавать произвольные данные. Эти же данные получаем в resolve/reject.

Далее подключаем модал в другом компоненте:

account.component.html
<app-yes-no-modal
  #deleteModal
  body="Вы действительно хотите удалить свой аккаунт?"
  title="Предупреждение"
  type="danger"
></app-yes-no-modal>
<button (click)="delete()">Удалить</button>

account.component.ts
import {Component, OnInit, ViewChild} from '@angular/core';
import {YesNoModalComponent} from '../_common/yes-no-modal/yes-no-modal.component';
import {DeleteUserEndpoint} from '../../api/delete-user.endpoint';
import {ErrorService} from '../../services/error/error.service';

@Component({
  selector: 'app-account',
  templateUrl: './account.component.html',
  styleUrls: ['./account.component.css'],
  providers: [DeleteUserEndpoint]
})
export class AccountComponent implements OnInit {


  @ViewChild('deleteModal') public deleteModal: YesNoModalComponent;

  constructor (private deleteUserEndpoint: DeleteUserEndpoint) {
  }

  delete(data) {
    this.deleteModal.showAsync(data).then(result => {
      this.deleteUserEndpoint.execute(result);
    });
  }

  ngOnInit(): void {
  }
}

На мой взгляд, использование такого решения выглядит максимально просто и читаемо.

Для сравнения, при использовании EventEmitter, придется определять два метода в каждом компоненте — showDeleteModal(), который вызывается кликом по кнопке удалить, и метод delete(), который вызывается по событию модала. Если у вас в одном компоненте будет несколько таких модалов на разные действия пользователя, то читаемость кода будет уже страдать.

В комментариях как всегда жду конструктивную и обоснованную критику.
Теги:
Хабы:
Всего голосов 6: ↑5 и ↓1+4
Комментарии5

Публикации

Истории

Работа

Ближайшие события