Source: Thoughtram Blog

Thoughtram Blog 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 opening an overlay. At the same time it’s an abstraction for some “heavy” lifting that should be implemented 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 array 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: <div class="cdk-overlay-container"> <div id="cdk-overlay-0" class="cdk-overlay-pane" dir="ltr"> <!-- Component goes here --> </div> </div> Done. That’s all we need to create our very first custom overlay using the CDK. Let’s try it out and see what we got so far: Our service only exposes one public method open() that will take care of creating a custom overlay. For now, the service is quite simple but it gets more complicated as we implement a more sophisticated and complete (functional-wise) overlay. Therefore it’s a good idea to extract the common logic into a service to stay DRY. Imagine we would have the same logic defined in each component we want to show an overlay. No good, right? Now that we have layed the foundation for our custom overlay, let’s take it one step further and improve on what we have so far. Let’s add a backdrop and specify a scroll and position strategy. Don’t worry if it’s unclear what scroll and position strategy is all about. We’ll cover that in a second. Configuring the overlay When creating an overlay, we can pass an optional configuration object to create() to set the desired options, e.g. whether it has backdrop, the position or scroll strategy, width, height and many more. Here’s an example: // Example configuration overlay.create({ width: '400px', height: '600px' }); First of all, we allow the consumer of our API to override certain options. Therefore, we update the signature for open() to also take a configuration object. In addition, we define an interface that describes the shape of the configuration from a consumer perspective: // Each property can be overridden by the consumer interface FilePreviewDialogConfig { panelClass?: string; hasBackdrop?: boolean; backdropClass?: string; } @Injectable() export class FilePreviewOverlayService { open(config: FilePreviewDialogConfig = {}) { ... } } Next, we define some initial values for the config, so that, by default, every overlay has a backdrop alongside a backdropClass and panelClass: const DEFAULT_CONFIG: FilePreviewDialogConfig = { hasBackdrop: true, backdropClass: 'dark-backdrop', panelClass: 'tm-file-preview-dialog-panel' } @Injectable() export class FilePreviewOverlayService { ... } With that in place, we can define a new method getOverlayConfig() which takes care of creating a new OverlayConfig for the custom overlay. Remember, it’s better to break down the logic into smaller parts instead of implementing everything in one giant function. This ensures better maintainabil

Read full article »
Est. Annual Revenue
$100K-5.0M
Est. Employees
1-25
CEO Avatar

CEO

Update CEO

CEO Approval Rating

- -/100