Angular DialogService – Exploring Bootstrap (Part 2)

Home / Angular DialogService – Exploring Bootstrap (Part 2)

angular_small

An important aspect of any web application is displaying messages to users based on events. I like to use modals for this. Angular2’s Bootstrap components provide some basic functionality to facilitate the creation of Modal Dialogs.


For my purposes, I want my Angular2 DialogService to behave similarly to my Angular 1.x DialogService. Angular2’s life cycle and life cycle hooks are significantly different from earlier versions, though.

In my first iteration, my service is going to support only a single Confirm method. This Confirm method with use Angular2’s Boostrap services to display the model and return the Promise from the Modal Response. The service will use a Component to display the template and the template for the Component will be in-line.

The code for this is actually pretty simple.

import { Injectable }    from '@angular/core';
import { Observable }    from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/delay';
import {NgbModal, NgbModalOptions, NgbActiveModal,  ModalDismissReasons} from '@ng-bootstrap/ng-bootstrap';
import {Component, Input, OnInit, ApplicationRef, ChangeDetectorRef} from '@angular/core';

@Component({
  template: `
    <div class="modal-header">
      <h4 class="modal-title">{{ title }}</h4>
      <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
        <span aria-hidden="true">&times;</span>
      </button>
    </div>
    <div class="modal-body">
      <p>{{message}}</p>
    </div>
    <div class="modal-footer">
      <button type="button" class="btn btn-secondary" (click)="activeModal.close(false)">Cancel</button>
      <button type="button" class="btn btn-secondary" (click)="activeModal.close(true)">Ok</button>
    </div>
  `
})

export class DialogComponent implements OnInit {
  @Input() title;
  @Input() message;

  constructor(public activeModal: NgbActiveModal, public changeRef: ChangeDetectorRef) {
    console.log("DialogComponent construct");
  }
  
  ngOnInit() {
    console.log("DialogComponent init");
  }
}

@Injectable()
export class DialogService {  
  constructor(private modalService: NgbModal) {}
  
  public confirm() {
    const modalRef = this.modalService.open(DialogComponent);
    modalRef.componentInstance.name = "Discard Changes?";
    modalRef.componentInstance.message = "Are you sure you want to discard your changes?";
    modalRef.componentInstance.changeRef.markForCheck();
    return modalRef.result;
  }
}

You can see that the Modal itself has a couple of inputs to manage/customize the Title and Message. The basic buttons are Cancel/Ok which, on clicking, utilize the “activeModal” service with true/false return values to indicate accepting or cancelling. The Confirm method returns the Promise that is generated when the modalService’s “open” method is called. Also note that the open method accepts the component that we want to render.

Calling the confirmation dialog, then, would be a matter of injecting the service and calling the “confirm” method. One wrinkle I ran into while writing this was that if I called the service outside of a UI event (click, etc), then the DialogComponent would actually not initialize. This is why you’ll see the injection of ChangeDetectorRef and that the call to “markForCheck()” has to be made. With this in place, the DialogService can be called as needed and work properly.

In your app.module, the DialogComponent and the DialogService have to be referenced. Additionally, the Angular2 Modal services require, when you want to render Components, that the Component be part of the ‘entryComponents’ collection.

import { Component, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { JsonpModule } from '@angular/http';
import { AppComponent, EqualPipe } from './app.component';
import { Route1Component } from './route1.component';
import { Route2Component } from './route2.component';
import { Route3Component }  from './route3.component';
import { APP_BASE_HREF } from '@angular/common';
import { NavigationService } from './services/navigation.service';
import { DialogService, DialogComponent } from './services/dialog.service';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

@NgModule({
  imports: [BrowserModule, FormsModule, ReactiveFormsModule, JsonpModule, NgbModule.forRoot(), routing], 
  declarations: [ AppComponent, Route1Component, Route2Component, Route3Component, DialogComponent, Multiselect, FilterPipe, EqualPipe ],
  bootstrap:    [ AppComponent ],
  providers: [ {provide: APP_BASE_HREF, useValue : document.location.pathname }, NavigationService, DialogService],
  entryComponents: [DialogComponent]
})

That’s all there is to it at this point. I do not have plunk for this DialogService, specifically, but do have a forth coming post that will utilize this service in conjunction with Menus, Navigation, and Route Guards that should pretty illustrative.

2 thoughts on “Angular DialogService – Exploring Bootstrap (Part 2)”

    1. That error message is pretty vague and had digging for a bit. The underlying issue is that calling “modal.dismiss(‘Cross Click’)” causes a promise rejection and, it seems, an exception. You have to catch any exceptions from the promise or the “zone” engine throws the exception you see in the console.

      I don’t know how you’re managing your promises, but you can see how I am handling it in my demo plunker that uses the dialogservice here:

      https://long2know.com/2017/04/angular-custom-table-component-paging/

      If you click between the menu items to change your current route, you will see the dialog. Within the navigationService, you can see that i take the dialogservice’s Promise and convert it to an Observable (fromPromise) in the “intercept” method. For the Observable, I only needed to add a “.catch” to the construction of the Observable. If you use the Promise only, you would work in a catch within your promise handling.

      Hope that helps!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.