DEV Community

Cover image for Implement Enter/Exit Animations with Angular CDK Overlay
Dharmen Shah for Angular Material Dev

Posted on • Originally published at angular-material.dev

Implement Enter/Exit Animations with Angular CDK Overlay

Angular CDK's Overlay module provides a powerful way to create floating panels that appear over your application's content. These overlays are commonly used for dropdowns, tooltips, modals, and context menus. While overlays work great out of the box, adding smooth enter and exit animations can significantly enhance the user experience.

In this article, we'll explore how to implement sophisticated enter/exit animations with Angular CDK Overlay using signals, dynamic animation classes and understand the true power of new Angular Animations's APIs.

What is Angular CDK Overlay?

The Angular CDK (Component Dev Kit) Overlay is a module that allows you to open floating panels on top of the current page. Unlike traditional CSS positioning, the Overlay module handles:

  • Positioning: Automatically positions the overlay relative to a trigger element
  • Repositioning: Dynamically adjusts position when the overlay doesn't fit in the viewport
  • Backdrop Management: Provides optional backdrop with click-outside handling
  • Z-Index Management: Manages stacking context for multiple overlays
  • Scroll Handling: Repositions or closes overlays on scroll

Prerequisites

Before we begin, ensure you have Angular CDK installed:

ng add @angular/cdk
Enter fullscreen mode Exit fullscreen mode

And the basic styles for the overlay are imported correctly in your application:

@import '@angular/cdk/overlay-prebuilt.css';
Enter fullscreen mode Exit fullscreen mode

Creating the Overlay Wrapper Component

Let's build a reusable overlay wrapper component that handles animations automatically based on the overlay's position.

Component Structure

First, let's create the component with signals for reactive state management:

import {
  CdkConnectedOverlay,
  CdkOverlayOrigin,
  STANDARD_DROPDOWN_BELOW_POSITIONS,
  ConnectedOverlayPositionChange,
  ConnectedPosition,
} from '@angular/cdk/overlay';
import {
  Component,
  ChangeDetectionStrategy,
  input,
  computed,
  signal,
} from '@angular/core';

@Component({
  selector: 'app-overlay-wrapper',
  templateUrl: './overlay-wrapper.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [CdkConnectedOverlay],
})
export class OverlayWrapperComponent {
  // Input signals for component configuration
  readonly trigger = input.required<CdkOverlayOrigin>();
  readonly positions = input<ConnectedPosition[]>([]);
  readonly panelClass = input<string>('');

  // State signals
  readonly isOpen = signal(false);
  readonly animationDirection = signal<'up' | 'down'>('down');

  // Computed animation classes based on direction
  readonly enterAnimationClass = computed(() => {
    const direction = this.animationDirection();
    return direction === 'up' ? 'animate-fade-in-up' : 'animate-fade-in-down';
  });

  readonly leaveAnimationClass = computed(() => {
    const direction = this.animationDirection();
    return direction === 'up' ? 'animate-fade-out-down' : 'animate-fade-out-up';
  });

  // Computed overlay positions with offset
  readonly overlayPositions = computed(() =>
    STANDARD_DROPDOWN_BELOW_POSITIONS.concat(this.positions()).map(item => ({
      ...item,
      offsetY: item.overlayY === 'bottom' ? -8 : 8,
    }))
  );

  toggleOverlay(): void {
    this.isOpen.update(value => !value);
  }

  close(): void {
    this.isOpen.set(false);
  }

  onPositionChange(event: ConnectedOverlayPositionChange): void {
    this.animationDirection.set(
      event.connectionPair.overlayY === 'bottom' ? 'up' : 'down'
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Features Explained

  1. Signals for Reactive State: We use Angular signals for state management, making the component reactive and efficient.
  2. Dynamic Animation Classes: The component automatically determines whether the overlay opens upward or downward and applies appropriate animation classes.
  3. Position Tracking: The onPositionChange method tracks the overlay's position, allowing us to adjust animations accordingly.
  4. Flexible Configuration: Input signals allow customization of trigger element, positions, and panel classes.

Instead of STANDARD_DROPDOWN_BELOW_POSITIONS, you can also use STANDARD_DROPDOWN_ADJACENT_POSITIONS to position the overlay adjacent to the trigger element. To learn more about how positioning works, see the official Angular CDK Overlay documentation.

Template Implementation

Now let's create the template that uses the CDK Connected Overlay:

<ng-template
  cdkConnectedOverlay
  [cdkConnectedOverlayOrigin]="trigger()"
  [cdkConnectedOverlayOpen]="isOpen()"
  [cdkConnectedOverlayPositions]="overlayPositions()"
  [cdkConnectedOverlayHasBackdrop]="true"
  [cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
  [cdkConnectedOverlayPanelClass]="panelClass()"
  (backdropClick)="close()"
  (positionChange)="onPositionChange($event)">
  <div
    [animate.enter]="enterAnimationClass()"
    [animate.leave]="leaveAnimationClass()">
    <ng-content />
  </div>
</ng-template>
Enter fullscreen mode Exit fullscreen mode

Template Features

  • cdkConnectedOverlay: The main directive that creates the overlay
  • cdkConnectedOverlayOrigin: References the trigger element
  • cdkConnectedOverlayOpen: Controls overlay visibility
  • cdkConnectedOverlayPositions: Array of preferred positions
  • cdkConnectedOverlayHasBackdrop: Enables backdrop for click-outside detection
  • [cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'": Sets the backdrop class to transparent. Note that cdk-overlay-transparent-backdrop is a class that is provided by the Angular CDK library..
  • Content Projection: <ng-content /> allows flexible content insertion

The real power of new Angular Animations APIs

animate.enter and animate.leave are Angular's built-in animation APIs that apply CSS animation classes to elements during enter and exit transitions. These APIs are designed to apply animation classes only when elements are being added to or removed from the DOM.

The key benefit is that Angular automatically manages the element's lifecycle during animations. When using animate.leave, Angular waits for the animation to complete before removing the element from the DOM. In our case, this means the overlay content remains visible throughout the exit animation, and the framework handles the timing automatically—we don't need to write any extra code to delay the destruction or manually remove elements. This is all managed by Angular's animation system.

For more details, see the official Angular animations guide.

CSS Animation Classes

Now, let's implement the CSS animation classes. These animations provide smooth fade and slide effects:

/* Base animation configuration */
.animate-fade-in-up,
.animate-fade-in-down,
.animate-fade-out-up,
.animate-fade-out-down {
  animation-duration: 200ms;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  animation-fill-mode: both;
}

/* Fade in from bottom (sliding up) */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-fade-in-up {
  animation-name: fadeInUp;
}

/* Fade in from top (sliding down) */
@keyframes fadeInDown {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-fade-in-down {
  animation-name: fadeInDown;
}

/* Fade out to bottom (sliding down) */
@keyframes fadeOutDown {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(10px);
  }
}

.animate-fade-out-down {
  animation-name: fadeOutDown;
}

/* Fade out to top (sliding up) */
@keyframes fadeOutUp {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-10px);
  }
}

.animate-fade-out-up {
  animation-name: fadeOutUp;
}
Enter fullscreen mode Exit fullscreen mode

Animation Details

  • Duration: 200ms provides a snappy but smooth animation
  • Timing Function: cubic-bezier(0.4, 0, 0.2, 1) creates a natural ease-out effect
  • Transform Distance: 10px vertical movement is subtle yet noticeable
  • Opacity Transition: Fades from 0 to 1 (enter) or 1 to 0 (exit)

Using the Overlay Wrapper

Here's how to use the overlay wrapper component in your application:

import { Component, viewChild } from '@angular/core';
import { CdkOverlayOrigin } from '@angular/cdk/overlay';
import { OverlayWrapperComponent } from './overlay-wrapper.component';

@Component({
  selector: 'app-dropdown-example',
  template: `
    <button
      #trigger="cdkOverlayOrigin"
      cdkOverlayOrigin
      (click)="overlay.toggleOverlay()">
      Open Menu
    </button>

    <app-overlay-wrapper
      [trigger]="trigger"
      #overlay>
      <div class="menu-content">
        <button (click)="overlay.close()">Option 1</button>
        <button (click)="overlay.close()">Option 2</button>
        <button (click)="overlay.close()">Option 3</button>
      </div>
    </app-overlay-wrapper>
  `,
  imports: [CdkOverlayOrigin, OverlayWrapperComponent],
})
export class DropdownExampleComponent {
  overlay = viewChild.required(OverlayWrapperComponent);
}
Enter fullscreen mode Exit fullscreen mode

Additional Styling

Add some basic styling for your overlay content:

.dropdown-panel {
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  min-width: 200px;
}

.menu-content {
  padding: 8px 0;
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.menu-content button {
  display: block;
  width: 100%;
  padding: 12px 16px;
  text-align: left;
  border: none;
  background: none;
  cursor: pointer;
  transition: background-color 150ms;
}

.menu-content button:hover {
  background-color: rgba(0, 0, 0, 0.04);
}
Enter fullscreen mode Exit fullscreen mode

Looking for more?

Head out to Flyout Menus by Angular Material Blocks for a more advanced implementation of flyout menus with Angular CDK Overlay.

flyout menu examples on ui.angular-material.dev

Conclusion

Implementing enter/exit animations with Angular CDK Overlay enhances the user experience by providing visual feedback and smooth transitions. By combining Angular's signals with CDK's positioning system, we've created a reusable component that:

  • Automatically adjusts animations based on overlay position
  • Provides a clean, reactive API using signals
  • Supports flexible configuration through input signals
  • Maintains performance with OnPush change detection

The approach demonstrated here can be adapted for various use cases including dropdowns, tooltips, context menus, and custom floating panels. The key is to keep animations subtle, performant, and purposeful.

Happy coding! 🚀

Live Playground

Top comments (0)