I’m over Angular Material. Actually, I’m done with the Material side of that equation. I’m working my way back into development and I’ve had this mental block that kept me from the realization that Angular’s CDK library is the perfect solution for anyone who’s been frustrated by Material’s opinionated css and deep class bullshit.
If you aren’t already aware, Angular CDK is close to a drop in replacement for Angular Material. As a test of that theory, I decided to swap out MatDialog with Angular CDK Dialog. From a purely logical standpoint it was trivial. Every part of passing and returning data operated as expected. Even styles went smoothly It got interesting when I wanted to alter the position of the Dialog.
By default, the /cdk/dialog positions itself in the center of the screen left to right and top to bottom. I prefer dialogs to be offset 16px from the top of the screen. Looking for answers using very specific search terms yielded solutions that relied on /cdk/overlay with no reference to CDK Dialog. As a developer, this is usually a flag for me. Why are there no articles talking about CDK Dialog? Refactoring was such a breeze that is seems to me the perfect route for anyone looking to release themselves from the ties of Material design.
Ultimately, I had to figure out a solution on my own. There was a lot of trial and error, but I thought posting it here might help someone in the future. I’m a huge fan of tailwind for css so you will see styling as css, but you can solve this just as easily with vanilla css as well.
The Problem: Switch MatDialog to CdlDialog and position the dialog 16px from top.
Here is the correct import. Make sure you npm install the Angular CDK first.
npm i @angular/cdk
Import the module.
import { DialogModule } from '@angular/cdk/dialog';
Add to your ngModel imports
imports: [
CommonModule,
DialogModule,
],
If you follow the guide below this is what your final confirm-dialog.module.ts will look like:
import { CommonModule } from '@angular/common';
import { NgModule} from '@angular/core';
import { DialogModule } from '@angular/cdk/dialog';
import { ConfirmDialogService } from './confirm-dialog.service';
import { ConfirmDialogComponent } from './confirm-dialog.component';
@NgModule({
imports: [
CommonModule,
DialogModule,
],
declarations: [
ConfirmDialogComponent
],
exports: [ConfirmDialogComponent],
entryComponents: [ConfirmDialogComponent],
providers: [ConfirmDialogService]
})
export class ConfirmDialogModule {
}
I’m going to seque for a moment to talk about the importance of having reusable generic components. Confirmation dialogs are one of the best use cases for reuse because they have a consistent structure:
Header with title text.
Content body usually with a short message.
Footer with Cancel and Confirm Buttons.
I found this very comprehensive guide for creating a reusable component complete with a service and I was able to create it in under a day and start migrating countless dialogs. Keep in mind that her guide uses MatDialog, but everything translates to cdk/dialog perfectly so if you are trying to create a reusable confirm dialog, check it out.
And we’re back…
As stated earlier, there were no issues with swapping out MatDialog with CdkDialog, but CdkDialog rendered centered vertically and horizontally.
So I looked at the docs and here is the extent of positionStrategy
I was somewhat familiar with this from MatDialog, but as it turn out, CdkDialog doesn’t support positionStrategy in the same way. Here is the configuration file that I used for MatDialog previously:
export const matDialogDefaultOptions: MatDialogConfig = {
minWidth: '415px',
maxWidth: `calc(100vw - 30px)`,
position: { top: '15px' },
hasBackdrop: true,
closeOnNavigation: true,
disableClose: false,
enterAnimationDuration: "200ms",
exitAnimationDuration: "200ms",
};
Check out the position:
parameter. That attribute doesn’t exist on the positionStrategy for CdkDialog. I have no idea why. This led me to search the internet only to discover that apparently everyone uses /cdk/overlay instead of MatDialog. I’m stubborn and lazy. I had already swapped out 10 dialog components and they worked perfectly so I decided to dig in an figure out what was going on.
I thought about writing about my thought process and my various iterations to figure it out but then I figured very few people would care, so I’m going to jump straight to the solution.
Set your CdkDialog options to:
width: '100vw',
height: '100vh',
This is telling the service to default to 100% viewport height and 100% viewport width. Viewport is powerful because it targets the actual edges of the browser window regardless of what may be shown on screen.
Your service file should have something like this including any data atrributes you may be sending in. Here is my entire confirm-dialog.service.ts file:
import { Injectable } from '@angular/core';
import { Dialog, DialogRef } from '@angular/cdk/dialog';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { ConfirmDialogComponent } from './confirm-dialog.component';
@Injectable()
export class ConfirmDialogService {
constructor(private dialog: Dialog) { }
dialogRef: DialogRef<ConfirmDialogComponent>;
public open(options: Record<any,any> = {}) {
this.dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '100vw',
height: '100vh',
data: {
title: options.title || 'Title',
message: options.message || 'Message',
cancel: {
text: options.cancel?.text || 'Cancel',
style: options.cancel?.style || 'btn-secondary'
},
confirm: {
text: options.confirm?.text || 'Confirm',
style: options.confirm?.style || 'btn-warning'
}
}
});
}
public confirmed(): Observable<any> {
return this.dialogRef.closed.pipe(take(1), map(res => {
return res;
}
));
}
}
Use flex and margin-top:4 on the :block level of your confirm.dialog.component.scss file. As you can see I’ve used tailwind syntax and the apply command to convert tailwind to css. The flex context is just a simple row flex centering all content horizontally on that row. The mt-4 is a tailwind way to say margin-top:16px. Tailwind uses 4x as a multiplier for all their styles.
In case you aren’t aware, the :host target is special with angular modules. It allows you to wrap the entire module in that css without creating another enclosing div. It’s a pretty handy tool when you need it. And I needed it in this case.
:host-context {
@apply flex justify-center mt-4
}
So, that’s it! Everything else I tried to position CdkDialog to the top of the screen (and centered) failed miserably. I could have saved hours if the position: parameter could have been used. Maybe they will add that back in at some point?
Happy programming!