LP1823041 Angular dialogs return observables
authorBill Erickson <berickxx@gmail.com>
Wed, 3 Apr 2019 16:14:52 +0000 (12:14 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 20 Jun 2019 14:17:31 +0000 (10:17 -0400)
Dialog.open() now returns an observable to the caller.  This allows
dialogs to pass 0 or more success events, error events, and close events
each as descrete actions to the caller.

Existing dialogs are updated to expect an Observable response to
.open().

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
19 files changed:
Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html
Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html
Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts
Open-ILS/src/eg2/src/app/share/dialog/progress.component.html
Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.html
Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts
Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-list.component.ts
Open-ILS/src/eg2/src/app/staff/cat/vandelay/queue.component.ts
Open-ILS/src/eg2/src/app/staff/catalog/record/part-merge-dialog.component.html
Open-ILS/src/eg2/src/app/staff/catalog/record/parts.component.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts
Open-ILS/src/eg2/src/app/staff/share/buckets/bucket-dialog.component.html
Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html
Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html
Open-ILS/src/eg2/tsconfig.json

index 82ed72a..ae584ce 100644 (file)
@@ -2,8 +2,7 @@
   <div class="modal-header bg-info">
     <h4 class="modal-title" i18n>Access Key Assignments</h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
index 21766ca..3db73cc 100644 (file)
@@ -2,16 +2,15 @@
   <div class="modal-header bg-info">
     <h4 class="modal-title">{{dialogTitle}}</h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
   <div class="modal-body"><p>{{dialogBody}}</p></div>
   <div class="modal-footer">
     <button type="button" class="btn btn-success" 
-      (click)="close('confirmed')" i18n>Confirm</button>
+      (click)="close(true)" i18n>Confirm</button>
     <button type="button" class="btn btn-warning" 
-      (click)="dismiss('canceled')" i18n>Cancel</button>
+      (click)="close(false)" i18n>Cancel</button>
   </div>
 </ng-template>
index e17fe8d..79a5c86 100644 (file)
@@ -1,19 +1,32 @@
 import {Component, Input, OnInit, ViewChild, TemplateRef, EventEmitter} from '@angular/core';
+import {Observable, Observer} from 'rxjs';
 import {NgbModal, NgbModalRef, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
 
 /**
  * Dialog base class.  Handles the ngbModal logic.
  * Sub-classed component templates must have a #dialogContent selector
  * at the root of the template (see ConfirmDialogComponent).
+ *
+ * Dialogs interact with the caller via Observable.
+ *
+ * dialog.open().subscribe(
+ *   value => handleValue(value),
+ *   error => handleError(error),
+ *   ()    => console.debug('dialog closed')
+ * );
+ *
+ * It is up to the dialog implementer to decide what values to
+ * pass to the caller via the dialog.respond(data) and/or
+ * dialog.close(data) methods.
+ *
+ * dialog.close(...) closes the modal window and completes the
+ * observable, unless an error was previously passed, in which
+ * case the observable is already complete.
+ *
+ * dialog.close() with no data closes the dialog without passing
+ * any values to the caller.
  */
 
-export interface DialogRejectionResponse {
-    // Did the user simply close the dialog without performing an action.
-    dismissed?: boolean;
-    // Relays error, etc. messages from the dialog handler to the caller.
-    message?: string;
-}
-
 @Component({
     selector: 'eg-dialog',
     template: '<ng-template></ng-template>'
@@ -32,6 +45,9 @@ export class DialogComponent implements OnInit {
     // called in the overridding method.
     onOpen$ = new EventEmitter<any>();
 
+    // How we relay responses to the caller.
+    observer: Observer<any>;
+
     // The modalRef allows direct control of the modal instance.
     private modalRef: NgbModalRef = null;
 
@@ -41,11 +57,11 @@ export class DialogComponent implements OnInit {
         this.onOpen$ = new EventEmitter<any>();
     }
 
-    async open(options?: NgbModalOptions): Promise<any> {
+    open(options?: NgbModalOptions): Observable<any> {
 
         if (this.modalRef !== null) {
-            console.warn('Dismissing existing dialog');
-            this.dismiss();
+            this.error('Dialog was replaced!');
+            this.finalize();
         }
 
         this.modalRef = this.modalService.open(this.dialogContent, options);
@@ -55,49 +71,62 @@ export class DialogComponent implements OnInit {
             setTimeout(() => this.onOpen$.emit(true));
         }
 
-        return new Promise( (resolve, reject) => {
+        return new Observable(observer => {
+            this.observer = observer;
 
             this.modalRef.result.then(
-                (result) => {
-                    resolve(result);
-                    this.modalRef = null;
-                },
-
-                (result) => {
-                    // NgbModal creates some result values for us, which
-                    // are outside of our control.  Other dismissal
-                    // reasons are agreed upon by implementing subclasses.
-                    console.debug('dialog closed with ' + result);
-
-                    const dismissed = (
-                           result === 0 // body click
-                        || result === 1 // Esc key
-                        || result === 'canceled' // Cancel button
-                        || result === 'cross_click' // modal top-right X
-                    );
-
-                    const rejection: DialogRejectionResponse = {
-                        dismissed: dismissed,
-                        message: result
-                    };
-
-                    reject(rejection);
-                    this.modalRef = null;
-                }
+                // Results are relayed to the caller via our observer.
+                // Our Observer is marked complete via this.close().
+                // Nothing to do here.
+                result => {},
+
+                // Modal was dismissed via UI control which
+                // bypasses call to this.close()
+                dismissed => this.finalize()
             );
         });
     }
 
-    close(reason?: any): void {
-        if (this.modalRef) {
-            this.modalRef.close(reason);
+    // Send a response to the caller without closing the dialog.
+    respond(value: any) {
+        if (this.observer && value !== undefined) {
+            this.observer.next(value);
+        }
+    }
+
+    // Sends error event to the caller and closes the dialog.
+    // Once an error is sent, our observable is complete and
+    // cannot be used again to send any messages.
+    error(value: any, close?: boolean) {
+        if (this.observer) {
+            console.error('Dialog produced error', value);
+            this.observer.error(value);
+            this.observer = null;
         }
+        if (this.modalRef) { this.modalRef.close(); }
+        this.finalize();
+    }
+
+    // Close the dialog, optionally with a value to relay to the caller.
+    // Calling close() with no value simply dismisses the dialog.
+    close(value?: any) {
+        this.respond(value);
+        if (this.modalRef) { this.modalRef.close(); }
+        this.finalize();
+    }
+
+    dismiss() {
+        console.warn('Dialog.dismiss() is deprecated.  Use close() instead');
+        this.close();
     }
 
-    dismiss(reason?: any): void {
-        if (this.modalRef) {
-            this.modalRef.dismiss(reason);
+    // Clean up after closing the dialog.
+    finalize() {
+        if (this.observer) { // null if this.error() called
+            this.observer.complete();
+            this.observer = null;
         }
+        this.modalRef = null;
     }
 }
 
index 78ca3d0..c1fdf20 100644 (file)
@@ -3,7 +3,7 @@
     <h4 *ngIf="dialogTitle" class="modal-title">{{dialogTitle}}</h4>
     <button type="button" class="close" 
       i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
index 1d7936b..17a6b50 100644 (file)
@@ -2,8 +2,7 @@
   <div class="modal-header bg-info">
     <h4 class="modal-title">{{dialogTitle}}</h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
@@ -17,6 +16,6 @@
     <button type="button" class="btn btn-success" 
       (click)="close(promptValue)" i18n>Confirm</button>
     <button type="button" class="btn btn-warning" 
-      (click)="dismiss('canceled')" i18n>Cancel</button>
+      (click)="close()" i18n>Cancel</button>
   </div>
 </ng-template>
index 1b0935f..5db749a 100644 (file)
@@ -5,13 +5,18 @@
   <div class="modal-header bg-info">
     <h4 class="modal-title" i18n>Record Editor: {{recordLabel}}</h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
   <div class="modal-body">
     <form #fmEditForm="ngForm" role="form" class="form-validated common-form striped-odd">
+      <ng-container *ngIf="!record">
+        <!-- display a progress dialog while the editor 
+            fetches the needed data -->
+        <eg-progress-inline></eg-progress-inline> 
+      </ng-container>
+      <ng-container *ngIf="record">
       <div class="form-group row" *ngFor="let field of fields">
         <div class="col-lg-3">
           <label for="{{idPrefix}}-{{field.name}}">{{field.label}}</label>
           </a>
         </div>
       </div>
+      </ng-container>
     </form>
   </div>
   <div class="modal-footer">
index e9a4531..e283cc2 100644 (file)
@@ -6,7 +6,7 @@ import {map} from 'rxjs/operators';
 import {AuthService} from '@eg/core/auth.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {DialogComponent} from '@eg/share/dialog/dialog.component';
-import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 import {TranslateComponent} from '@eg/staff/share/translate/translate.component';
 
@@ -173,14 +173,8 @@ export class FmRecordEditorComponent
 
         // Add some randomness to the generated DOM IDs to ensure against clobbering
         this.idPrefix = 'fm-editor-' + Math.floor(Math.random() * 100000);
-    }
 
-    // Opening dialog, fetch data.
-    open(options?: NgbModalOptions): Promise<any> {
-        return this.initRecord().then(
-            ok => super.open(options),
-            err => console.warn(`Error fetching FM data: ${err}`)
-        );
+        this.onOpen$.subscribe(() => this.initRecord());
     }
 
     // Set the record value and clear the recId value to
@@ -467,12 +461,12 @@ export class FmRecordEditorComponent
         this.convertDatatypesToIdl(recToSave);
         this.pcrud[this.mode]([recToSave]).toPromise().then(
             result => this.close(result),
-            error  => this.dismiss(error)
+            error  => this.error(error)
         );
     }
 
     cancel() {
-        this.dismiss('canceled');
+        this.close();
     }
 
     // Returns a string describing the type of input to display
index 3af756c..1bb80fa 100644 (file)
@@ -2,8 +2,7 @@
   <div class="modal-header bg-info">
     <h4 class="modal-title" i18n>Grid Columns Configuration</h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
index a5c72e2..5ce77d4 100644 (file)
@@ -121,16 +121,14 @@ export class WorkstationsComponent implements OnInit {
 
     private handleCollision(): Promise<number> {
         return new Promise((resolve, reject) => {
-            this.wsExistsDialog.open()
-            .then(
-                confirmed => {
+            this.wsExistsDialog.open().subscribe(override => {
+                if (override) {
                     this.registerWorkstationApi(true).then(
                         wsId => resolve(wsId),
                         notOk => reject(notOk)
                     );
-                },
-                dismissed => reject(dismissed)
-            );
+                }
+            });
         });
     }
 
index 0afc01d..c33999a 100644 (file)
@@ -41,10 +41,8 @@ export class MatchSetListComponent implements AfterViewInit {
 
         this.createNew = () => {
             this.editDialog.mode = 'create';
-            this.editDialog.open({size: 'lg'}).then(
-                ok => this.grid.reload(),
-                err => {}
-            );
+            this.editDialog.open({size: 'lg'})
+                .subscribe(() => this.grid.reload());
         };
 
         this.deleteSelected = (matchSets: IdlObject[]) => {
@@ -62,10 +60,8 @@ export class MatchSetListComponent implements AfterViewInit {
             (matchSet: IdlObject) => {
                 this.editDialog.mode = 'update';
                 this.editDialog.recId = matchSet.id();
-                this.editDialog.open({size: 'lg'}).then(
-                    ok => this.grid.reload(),
-                    err => {}
-                );
+                this.editDialog.open({size: 'lg'})
+                    .subscribe(() => this.grid.reload());
             }
         );
     }
index 9055f21..19f08ad 100644 (file)
@@ -215,35 +215,26 @@ export class QueueComponent implements OnInit, AfterViewInit {
     }
 
     deleteQueue() {
-        this.confirmDelDlg.open().then(
-            yes => {
-                this.progressDlg.open();
-                return this.net.request(
-                    'open-ils.vandelay',
-                    `open-ils.vandelay.${this.qtypeShort()}_queue.delete`,
-                    this.auth.token(), this.queueId
-                ).toPromise();
-            },
-            no => {
-                this.progressDlg.close();
-                return Promise.reject('delete failed');
-            }
-        ).then(
-            resp => {
-                this.progressDlg.close();
-                const e = this.evt.parse(resp);
-                if (e) {
-                    console.error(e);
-                    alert(e);
-                } else {
+
+        this.confirmDelDlg.open().subscribe(confirmed => {
+            if (!confirmed) { return; }
+
+            this.progressDlg.open();
+            this.net.request(
+                'open-ils.vandelay',
+                `open-ils.vandelay.${this.qtypeShort()}_queue.delete`,
+                this.auth.token(), this.queueId
+            ).toPromise().then(
+                resp => {
+                    const e = this.evt.parse(resp);
+                    if (e) { return new Error(e.toString()); }
+
                     // Jump back to the main queue page.
                     this.router.navigate(['/staff/cat/vandelay/queue']);
-                }
-            },
-            err => {
-                this.progressDlg.close();
-            }
-        );
+                },
+                err => console.error('queue deletion failed!', err)
+            ).finally(() => this.progressDlg.close());
+        });
     }
 
     exportNonImported() {
index ef702eb..0beeefc 100644 (file)
@@ -2,8 +2,7 @@
   <div class="modal-header bg-info">
     <h4 class="modal-title" i18n>Merge Monograph Parts</h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
@@ -23,6 +22,6 @@
     <button type="button" class="btn btn-success" 
       (click)="mergeParts()" i18n>Merge</button>
     <button type="button" class="btn btn-warning" 
-      (click)="dismiss('canceled')" i18n>Cancel</button>
+      (click)="close()" i18n>Cancel</button>
   </div>
 </ng-template>
index 3ab8e8f..2f59374 100644 (file)
@@ -81,10 +81,8 @@ export class PartsComponent implements OnInit {
             (part: IdlObject) => {
                 this.editDialog.mode = 'update';
                 this.editDialog.recId = part.id();
-                this.editDialog.open().then(
-                    ok => this.partsGrid.reload(),
-                    err => {}
-                );
+                this.editDialog.open()
+                    .subscribe(ok => this.partsGrid.reload());
             }
         );
 
@@ -95,10 +93,7 @@ export class PartsComponent implements OnInit {
             this.editDialog.record = part;
 
             this.editDialog.mode = 'create';
-            this.editDialog.open().then(
-                ok => this.partsGrid.reload(),
-                err => {}
-            );
+            this.editDialog.open().subscribe(ok => this.partsGrid.reload());
         };
 
         this.deleteSelected = (parts: IdlObject[]) => {
@@ -113,10 +108,7 @@ export class PartsComponent implements OnInit {
         this.mergeSelected = (parts: IdlObject[]) => {
             if (parts.length < 2) { return; }
             this.mergeDialog.parts = parts;
-            this.mergeDialog.open().then(
-                ok => this.partsGrid.reload(),
-                err => console.debug('Dialog dismissed')
-            );
+            this.mergeDialog.open().subscribe(ok => this.partsGrid.reload());
         };
     }
 }
index 6d4e2ea..de94b5e 100644 (file)
@@ -158,15 +158,10 @@ export class SandboxComponent implements OnInit {
     }
 
     openEditor() {
-        this.fmRecordEditor.open({size: 'lg'}).then(
-            ok => { console.debug(ok); },
-            err => {
-                if (err && err.dismissed) {
-                    console.debug('dialog was dismissed');
-                } else {
-                    console.error(err);
-                }
-            }
+        this.fmRecordEditor.open({size: 'lg'}).subscribe(
+            pcrudResult => console.debug('Record editor performed action'),
+            err => console.error(err),
+            () => console.debug('Dialog closed')
         );
     }
 
index 509f95c..2faf8f9 100644 (file)
@@ -171,6 +171,91 @@ export class AdminPageComponent implements OnInit {
         this.grid.onRowActivate.subscribe(
             (idlThing: IdlObject) => this.showEditDialog(idlThing)
         );
+
+        this.editSelected = (idlThings: IdlObject[]) => {
+
+            // Edit each IDL thing one at a time
+            const editOneThing = (thing: IdlObject) => {
+                if (!thing) { return; }
+
+                this.showEditDialog(thing).then(
+                    () => editOneThing(idlThings.shift()));
+            };
+
+            editOneThing(idlThings.shift());
+        };
+
+        this.createNew = () => {
+            this.editDialog.mode = 'create';
+            // We reuse the same editor for all actions.  Be sure
+            // create action does not try to modify an existing record.
+            this.editDialog.recId = null;
+            this.editDialog.record = null;
+            this.editDialog.open({size: this.dialogSize}).subscribe(
+                result => {
+                    this.createString.current()
+                        .then(str => this.toast.success(str));
+                    this.grid.reload();
+                },
+                error => {
+                    this.createErrString.current()
+                        .then(str => this.toast.danger(str));
+                }
+            );
+        };
+
+        this.deleteSelected = (idlThings: IdlObject[]) => {
+            idlThings.forEach(idlThing => idlThing.isdeleted(true));
+            this.pcrud.autoApply(idlThings).subscribe(
+                val => console.debug('deleted: ' + val),
+                err => {},
+                ()  => this.grid.reload()
+            );
+        };
+
+        // Open the field translation dialog.
+        // Link the next/previous actions to cycle through each translatable
+        // field on each row.
+        this.translate = () => {
+            this.translateRowIdx = 0;
+            this.translateFieldIdx = 0;
+            this.translator.fieldName = this.translatableFields[this.translateFieldIdx];
+            this.translator.idlObject = this.dataSource.data[this.translateRowIdx];
+
+            this.translator.nextString = () => {
+
+                if (this.translateFieldIdx < this.translatableFields.length - 1) {
+                    this.translateFieldIdx++;
+
+                } else if (this.translateRowIdx < this.dataSource.data.length - 1) {
+                    this.translateRowIdx++;
+                    this.translateFieldIdx = 0;
+                }
+
+                this.translator.idlObject =
+                    this.dataSource.data[this.translateRowIdx];
+                this.translator.fieldName =
+                    this.translatableFields[this.translateFieldIdx];
+            };
+
+            this.translator.prevString = () => {
+
+                if (this.translateFieldIdx > 0) {
+                    this.translateFieldIdx--;
+
+                } else if (this.translateRowIdx > 0) {
+                    this.translateRowIdx--;
+                    this.translateFieldIdx = 0;
+                }
+
+                this.translator.idlObject =
+                    this.dataSource.data[this.translateRowIdx];
+                this.translator.fieldName =
+                    this.translatableFields[this.translateFieldIdx];
+            };
+
+            this.translator.open({size: 'lg'});
+        };
     }
 
     checkCreatePerms() {
@@ -262,22 +347,24 @@ export class AdminPageComponent implements OnInit {
         return this.contextOrg && this.contextOrg.children().length === 0;
     }
 
-    showEditDialog(idlThing: IdlObject) {
+    showEditDialog(idlThing: IdlObject): Promise<any> {
         this.editDialog.mode = 'update';
         this.editDialog.recId = idlThing[this.pkeyField]();
-        return this.editDialog.open({size: this.dialogSize}).then(
-            ok => {
-                this.successString.current()
-                    .then(str => this.toast.success(str));
-                this.grid.reload();
-            },
-            rejection => {
-                if (!rejection.dismissed) {
+        return new Promise((resolve, reject) => {
+            this.editDialog.open({size: this.dialogSize}).subscribe(
+                result => {
+                    this.successString.current()
+                        .then(str => this.toast.success(str));
+                    this.grid.reload();
+                    resolve(result);
+                },
+                error => {
                     this.updateFailedString.current()
                         .then(str => this.toast.danger(str));
+                    reject(error);
                 }
-            }
-        );
+            );
+        });
     }
 
     editSelected(idlThings: IdlObject[]) {
index 32b6e2e..2c59548 100644 (file)
@@ -10,8 +10,7 @@
       <span *ngIf="fromBibQueue" i18n>Add Records from queue #{{fromBibQueue}} to Bucket</span>
     </h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
index e5a6f49..d472202 100644 (file)
@@ -2,8 +2,7 @@
   <div class="modal-header bg-info">
     <h4 class="modal-title" i18n>Change Operator</h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
@@ -60,6 +59,6 @@
   </div>
   <div class="modal-footer">
     <button (click)="login()" class="btn btn-info" i18n>OK/Continue</button>
-    <button (click)="dismiss('canceled')" class="btn btn-warning ml-2" i18n>Cancel</button>
+    <button (click)="close()" class="btn btn-warning ml-2" i18n>Cancel</button>
   </div>
 </ng-template>
index 7aa59b4..61b9cb4 100644 (file)
@@ -4,8 +4,7 @@
       {{idlClassDef.label}}
     </h4>
     <button type="button" class="close" 
-      i18n-aria-label aria-label="Close" 
-      (click)="dismiss('cross_click')">
+      i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
     </button>
   </div>
@@ -58,6 +57,6 @@
     <button *ngIf="nextString" (click)="nextString()" 
       class="btn btn-info mr-3" i18n>Next String</button>
     <button (click)="translate()" class="btn btn-info" i18n>Apply</button>
-    <button (click)="dismiss('canceled')" class="btn btn-warning ml-2" i18n>Cancel</button>
+    <button (click)="close()" class="btn btn-warning ml-2" i18n>Cancel</button>
   </div>
 </ng-template>
index 14a504d..157d6e6 100644 (file)
@@ -18,7 +18,8 @@
     ],
     "lib": [
       "es2017",
-      "dom"
+      "dom",
+      "es2018.promise"
     ]
   }
 }