Custom Overlays with Angular’s CDK

You have probably heared of Angular Material haven’t you? If you haven’t, it’s a library that provides you with high-quality Material Design components for Angular. Material Design itself is a visual design language that aims for consistency of user experience across all platforms and device sizes. That’s cool but what if your company has its own opinions about styles and the overall look and feel of the UI? How do we get the best of Angular Material without adopting the Material Design visual language?
Tada 🎉! That’s where Angular Material’s Component Dev Kit (CDK for short) comes into play. The CDK provides us with tools to build awesome and high-quality Angular components without adopting the Material Design visual language. Its goal is to make our life as developers easier and extract common behaviors and patterns shared between multiple Angular Material components. For instance, the datepicker, snackbar, or tooltip have something in common; they need to dynamically open up some floating panel on the screen. But that’s just the tip of the ice berg. There are many different packages for all sorts of things such as a11y that helps us improve the accessibility of our UI components. There’s even a layout package with utilities to build responsive UIs that react to screen-size changes. For a more complete list, please check out the official documentation.
Over at MachineLabs, we thought it would be useful to provide a way to preview generated output files (mostly images), so users don’t have to download it every single time just to take a quick look. So we sat down to build a Google Drive like overlay with the CDK. This post is meant to share our knowledge with the community and to make you comfortable using the CDK for your own purposes.
In this post, we’ll use the CDK to build a Google Drive-like custom overlay that looks and feels much like the one built for MachineLabs. Here’s how it looks like:

TABLE OF CONTENTS

The building blocks
Setup
Our first overlay
Configuring the overlay
Scroll strategy
Position strategy

Closing overlays with a remote control
Improving ergonomics
Sharing data with the overlay component
Where to go from here

The building blocks
Let’s start simple and work our way up to the final, fully-fledged solution which will have a very similar API as the MatDialog service provided by Angular Material. It’s not important to know exactly how the MatDialog works but it’s definitely helpful. If this is new to you, we recommend to check out our post on Easy Dialogs with Angular Material.
Our solution will be a little less flexible but specifically made for showing a file preview inspired by Google Drive. That said, we’d like to have a nice toolbar at the top and the image being rendered in the middle of the screen.
In general, the MatDialog is great for showing content in a dialog box but as soon as we want a little bit of a custom look and feel, something that does not look like a white box with content inside, we would need to roll our own overlay. Luckily, we can use the overlay package from the CDK that has most of the core logic for opening floating panels already baked in. More on that in just a second.
Here are the core building blocks of our application:

As we can see, we have two components, one service and a class that represents a remote control to an opened overlay. The AppComponent is the root (or entry point) of our application. This component contains a toolbar and the list of files that we can preview. In addition, it has access to a FilePreviewOverlayService which provides us with the core logic for opnening an overlay. At the same time it’s an abstraction for some “heavy” lifting that should be implemneted in a resuable manner. Don’t be scared, it’s not going to be super heavy and we’ll break it down into comprehensible chunks. Last but not least, there’s a FilePreviewOverlayRef which, as mentioned, is a handle used to control (e.g. close) a particular overlay.
For the overlay we choose to render a component, so we can attach some logic and also add animations to our overlay to engage our users and make them happy. We call this component FilePreviewOverlayComponent.
That’s about it. Now that we have the basic structure in place, we’re ready to look at some code.
Note that this post is the first part out of two in which we lay the foundation for our custom overlay. We’ll build on top of this in the next part and add keyboard support, image preloading and animations.
Setup
Before we can start implementing the custom overlay we need to install the CDK. Simply run npm install @angular/cdk and we’re all set!
Our first overlay
From the MatDialog we know that when we open an overlay we must specify a component type that is then created dynamically at runtime. This means it is not created by using the component tags inside an HTML template. Also, we know that whenever a component is created at runtime, we must add it to our application module’s entryComponents.
Let’s do that and add the FilePreviewOverlayComponent to the arry of entryComponents. In addition, we need to add the OverlayModule to the imports list of the root AppModule:
import { OverlayModule } from ‘@angular/cdk/overlay’;

@NgModule({
imports: [ … ],
declarations: [ …, FilePreviewOverlayComponent ],
bootstrap: [ AppComponent ],
providers: [ … ],
entryComponents: [
// Needs to be added here because otherwise we can’t
// dynamically render this component at runtime
FilePreviewOverlayComponent
]
})
export class AppModule { }
From there, creating an overlay is easy. First, we inject the Overlay service. This service has a create() function that we need to call in order to create a PortalHost for our FilePreviewOverlayComponent. Finally we need to create a ComponentPortal from this component and attach it to the PortalHost. Wait, what? Let’s give it a moment and look at some code before taking it apart:
@Injectable()
export class FilePreviewOverlayService {

// Inject overlay service
constructor(private overlay: Overlay) { }

open() {
// Returns an OverlayRef (which is a PortalHost)
const overlayRef = overlay.create();

// Create ComponentPortal that can be attached to a PortalHost
const filePreviewPortal = new ComponentPortal(FilePreviewOverlayComponent);

// Attach ComponentPortal to PortalHost
overlayRef.attach(filePreviewPortal);
}
}
The first step is to create a PortalHost. We do that by calling create() on the Overlay service. This will return an OverlayRef instance which is basically a remote control for the overlay. One unique attribute of this OverlayRef is that it’s a PortalHost, and once created, we can attach or detach Portals. We can think of a PortalHost as a placeholder for a component or template. So in our scenario, we are creating a ComponentPortal that takes a component type as its fist argument. In order to actually display this component we need to attach the portal to the host.

Ok, but where does the overlay get rendered?

Good question. There’s an OverlayContainer service which creates a container div under the hood that gets appended to the body of the HTML Document. There are a few more wrapper elements created but our component eventually ends up in a div with a class of cdk-overlay-pane. Here’s what the DOM structure looks like: