MDBootstrap meets Material CDK

Douglas Liu
Sohoffice
Published in
4 min readMar 19, 2019

I love bootstrap. This component and style framework offers a system to create good frontend. MDBootstrap is an initiative to allow us to use bootstrap in Angular. However, Bootstrap is more focused on the components and styles themself. To make use these components and use them elegantly, we need some support between the foundation and the components.

Angular Material CDK can be used to bridge the gap, it offers some very nice infrastructure to build comprehensive UI on Angular. Today, I’m looking at a specific issue I had: To create a confirm dialog without having to embed the mdbModal html every time a simple confirmation is needed.

Photo by Cytonn Photography on Unsplash

This is what it takes to create a mdbModal in MDBootstrap.

<div mdbModal #frame="mdbModal" class="modal fade" id="frameModalTop" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true" (open)="onOpen($event)">
<div class="modal-dialog modal-notify modal-success" role="document">
<div class="modal-content">
<div class="modal-header">
<p class="heading lead">Modal Success</p>

<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="frame.hide()">
<span aria-hidden="true" class="white-text">&times;</span>
</button>
</div>

<div class="modal-body">
I'm the modal body
</div>

<!--Footer-->
<div class="modal-footer justify-content-center">
<a type="button" mdbBtn color="primary" class="waves-light" mdbWavesEffect>Yes
<i class="far fa-gem ml-1"></i>
</a>
<a type="button" mdbBtn color="primary" outline="true" class="waves-effect" mdbWavesEffect (click)="frame.hide()"
data-dismiss="modal">No</a>
</div>
</div>
<!--/.Content-->
</div>
</div>

What a monster ! I honestly don’t think anyone would want to use the above just to create a Yes / No dialog.

Photo by Aarón Blanco Tejedor on Unsplash

The expected way to do a confirm dialog is a method that returns Observable<boolean>. Something similar to the below.

const obs$: Observable<boolean> = this.commonDialog
.openConfirmDialog('Please confirm', 'My message');

Take 1, An angular component

I started by creating a new component. The component receives a title and message as input, and emits a boolean as output.

<app-confirm-dialog #confirm title="Please confirm" message="My message"></app-confirm-dialog>

This can work, but not to the extent I want it to be. When I wanted a confirmation, I most likely want to behave according to the output. As a result, the code is most likely needed in Typescript method, not html template. To get the handle of this dialog, I’ll have to use a @ViewChild in the component class, and this is definitely not ideal.

Take 2, A service

I then read the source codes of MatDialog, trying to understand how these things work. It turns out we can use a service.

Wait, where should we put the modal content into if we’re using a service ?

Should we create an element under document body and remove it when the dialog was closed ?

Take 3, Overlay

The Material Overlay does exactly this, create an element under document body and attach your component into it. We can subscribe to the close event of mdbModal and detach the component from the Overlay.

The service code looks like this.

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

constructor(private overlay: Overlay,
private injector: Injector) {
}

openConfirmDialog(title: string, content: string | ComponentType<any> | TemplateRef<any>) {
const overlayRef = this.overlay.create(new OverlayConfig({
positionStrategy: this.overlay.position().global(),
hasBackdrop: false, disposeOnNavigation: true
}));
const modalComponent = new ComponentPortal(ConfirmDialogComponent, null, this.injector);
const ref = overlayRef.attach(modalComponent);
return ref.instance.display(title, content).pipe(
tap(() => {
// destroy the ref and detach from the overlay
ref.destroy();
overlayRef.detach();
})
);
}
}

A few explanations.

  1. We don’t care the position of the overlay. The actual position is controlled by mdbModal (encapsulated in ConfirmDialogComponent). What we want from Overlay is simply a place to put the mdbModal into.
  2. We don’t need the Overlay backdrop, mdbModal has it’s own backdrop anyway.
  3. The ConfirmDialogComponent.display() method is where I bind the dialog title and content, and return a subject. The subject will emit when ‘Yes’ or ‘No’ button is clicked. (Or if the dialog is closed without answer) I also use Material CDK Portal in this method so that the content can accept either another component or a template fragment.

Gotchas

A small tweak was needed for the dialog to work. The CDK Overlay has a z-index of 1000, which is very high. However, the z-index of the mdbModal backdrop is even higher with the value of 1040. You’ll have to either increase the z-index of cdk overlay or decrease the z-index of mdbModal backdrop.

.cdk-overlay-container {
z-index: 1050;
}

Final Results

This is how I use the confirm dialog in the end.

constructor(private commonDialog: CommonDialog) {}onAction(check: TemplateRef<any> | string) {
this.commonDialog.openConfirmDialog('Please confirm', check).pipe(
filter(identity),
tap(() => do_something())
).subscribe();
}

The other commonly used dialogs can be added in a similar way.

Did you learn something new? If so please:

clap 👏 button below️ so more people can see this

--

--

Douglas Liu
Sohoffice

Problem solver. Found love in Scala, Java, Angular and more …