All files / src/app/shared/components/common/modal-dialog modal-dialog.component.ts

82.14% Statements 23/28
66.66% Branches 8/12
75% Functions 6/8
81.48% Lines 22/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 14129x 29x                         29x   29x 29x                                                                                             29x     4x   4x   4x                 4x   4x   4x           2x   2x   2x     1x     2x             1x 1x             1x 1x                                         4x      
import { DOCUMENT } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  DestroyRef,
  EventEmitter,
  Inject,
  Input,
  OnDestroy,
  Output,
  TemplateRef,
  ViewChild,
  inject,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { NgbModal, NgbModalOptions, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { Subject, race, take } from 'rxjs';
 
export interface ModalOptions extends NgbModalOptions {
  /**
   * Modal title string.
   */
  titleText?: string;
  /**
   * Optional modal confirm button text.
   */
  confirmText?: string;
  /**
   * Optional modal confirm button disabled.
   */
  confirmDisabled?: boolean;
  /**
   * Optional modal reject button text.
   */
  rejectText?: string;
  /**
   * Optional icon properties to display an icon in front of the title, e.g. ['fas', 'triangle-exclamation'],
   */
  icon?: IconProp;
  /**
   * Optional icon styling classes, e.g. faIconClass: 'text-warning pr-2'
   */
  iconClass?: string;
}
 
/**
 * The Modal Dialog Component displays a generic (ng-bootstrap) modal, that shows a custom title and custom content.
 * It provides an optional footer that includes confirm and reject buttons.
 * It is possible to pass any data on show.
 * The also provided confirmed output emitter will return the previously passed data if the modal gets confirmed.
 *
 * @see https://ng-bootstrap.github.io/#/components/modal/api#NgbModal
 *
 * @example
 * <ish-modal-dialog [options]="options" (confirmed)="onConfirmed($event)">
 *   Dummy content
 * </ish-modal-dialog>
 */
@Component({
  selector: 'ish-modal-dialog',
  templateUrl: './modal-dialog.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ModalDialogComponent<T> implements OnDestroy {
  @Input({ required: true }) options: ModalOptions;
 
  @Output() confirmed = new EventEmitter<T>();
 
  @Output() closed = new EventEmitter<T>();
 
  @Output() shown = new EventEmitter<T>();
 
  @ViewChild('template') modalDialogTemplate: TemplateRef<unknown>;
 
  // visible-for-testing
  ngbModalRef: NgbModalRef;
 
  data: T;
 
  private hide$ = new Subject<void>();
 
  private destroyRef = inject(DestroyRef);
 
  constructor(private ngbModal: NgbModal, @Inject(DOCUMENT) private document: Document) {}
 
  /**
   * Configure and show modal dialog.
   */
  show(data?: T) {
    this.data = data ? data : undefined;
 
    this.ngbModalRef = this.ngbModal.open(this.modalDialogTemplate, this.options);
 
    race(this.ngbModalRef.dismissed, this.hide$)
      .pipe(take(1), takeUntilDestroyed(this.destroyRef))
      .subscribe(() => {
        this.closed.emit(this.data);
      });
 
    this.shown.emit(this.data);
  }
 
  /**
   * Hides modal dialog.
   */
  hide() {
    this.ngbModalRef.close();
    this.hide$.next();
  }
 
  /**
   * Emits input data or undefined and hides modal.
   */
  confirm() {
    this.confirmed.emit(this.data);
    this.hide();
  }
 
  /**
   * Scrolls to an anchor element
   *
   * @param anchor The ID of the anchor element.
   */
  // not-dead-code
  scrollToAnchor(anchor: string) {
    Iif (this.options.scrollable) {
      const element = this.document.getElementById(anchor);
      Iif (element) {
        setTimeout(() => {
          element.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
        }, 200);
      }
    }
  }
 
  ngOnDestroy(): void {
    this.hide$.complete(); // complete open hide$ subscription
  }
}