Search code examples
angularangular-cdk

Issue with Angular CDK overlay inside another


I am using the Angular CDK overlay to show a modal drawer on my page. I am also using the Angular CDK Overlay for tooltips. It's possible that I may have a control in my drawer with a tooltip. The drawer allows the user to close it by pressing Escape.

My issue is if I have the drawer open and the tooltip showing and I press Escape. The drawer closes but the tooltip remains visible.

This StackBlitz demonstrates the issue (open the drawer, hover to show the tooltip, and press the Escape key).

https://stackblitz.com/edit/angular-material-datepicker-sfpsg7?file=src%2Fapp%2Fapp.component.ts

How can I resolve this?


Solution

  • Two way to solve this problem.

    1. Take a export of the directive using exportAs and then call the close method manually.

    Full Code:

    App.ts

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'my-app',
      template: `
        <button (click)="drawer.open()">Open Drawer</button>
    
        <pro-drawer #drawer headerText="User Information" (dismissRequested)="drawer.close(); toolTip.hideTooltip();">
          <button style="margin: 150px 0 0 50px;" #toolTip="proTooltip" proTooltip="This is tooltip text">Hover for tooltip</button>
        </pro-drawer>
      `,
    })
    export class AppComponent {}
    

    directive.ts

    import {
      ComponentRef,
      Directive,
      ElementRef,
      HostListener,
      Input,
      OnDestroy,
      TemplateRef,
    } from '@angular/core';
    import { Overlay, OverlayRef } from '@angular/cdk/overlay';
    import { ComponentPortal } from '@angular/cdk/portal';
    import { ProTooltipComponent } from './pro-tooltip.component';
    
    @Directive({
      selector: '[proTooltip]',
      exportAs: 'proTooltip',
    })
    export class ProTooltipDirective implements OnDestroy {
      @Input('proTooltip') text = '';
    
      private overlayRef: OverlayRef;
      private tooltipVisible: boolean = false;
    
      constructor(private overlay: Overlay, private elementRef: ElementRef) {}
    
      ngOnDestroy() {
        this.hideTooltip();
      }
    
      @HostListener('mouseenter')
      onMouseEnter() {
        this.showTooltip();
      }
    
      @HostListener('mouseleave')
      onMouseLeave() {
        this.hideTooltip();
      }
    
      private showTooltip() {
        if (
          !this.tooltipVisible &&
          !this.overlayRef &&
          (!!this.text || !!this.template)
        ) {
          this.overlayRef = this.overlay.create({
            positionStrategy: this.overlay
              .position()
              .flexibleConnectedTo(this.elementRef)
              .withPositions([
                {
                  panelClass: 'tooltip-location-top',
                  originX: 'center',
                  originY: 'top',
                  overlayX: 'center',
                  overlayY: 'bottom',
                },
              ]),
          });
    
          const tooltipRef: ComponentRef<ProTooltipComponent> =
            this.overlayRef.attach(new ComponentPortal(ProTooltipComponent));
          tooltipRef.instance.text = this.text;
    
          this.tooltipVisible = true;
        }
      }
    
      private hideTooltip() {
        if (this.tooltipVisible && this.overlayRef) {
          this.overlayRef.detach();
          if (this.overlayRef) {
            this.overlayRef.dispose();
            this.overlayRef = null;
          }
          this.tooltipVisible = false;
        }
      }
    }
    

    Stackblitz Demo

    1. Listen to window keydown event using Hostlistener and trigger the close on press of escape button.

    Full Code:

    directive.ts

    import {
      ComponentRef,
      Directive,
      ElementRef,
      HostListener,
      Input,
      OnDestroy,
      TemplateRef,
    } from '@angular/core';
    import { Overlay, OverlayRef } from '@angular/cdk/overlay';
    import { ComponentPortal } from '@angular/cdk/portal';
    import { ProTooltipComponent } from './pro-tooltip.component';
    
    @Directive({
      selector: '[proTooltip]',
    })
    export class ProTooltipDirective implements OnDestroy {
      @Input('proTooltip') text = '';
    
      private overlayRef: OverlayRef;
      private tooltipVisible: boolean = false;
    
      constructor(private overlay: Overlay, private elementRef: ElementRef) {}
    
      ngOnDestroy() {
        this.hideTooltip();
      }
    
      @HostListener('mouseenter')
      onMouseEnter() {
        this.showTooltip();
      }
    
      @HostListener('mouseleave')
      onMouseLeave() {
        this.hideTooltip();
      }
    
      @HostListener('window:keydown.escape')
      onEscape() {
        console.log('window.keydown.escape');
        this.hideTooltip();
      }
    
      private showTooltip() {
        if (
          !this.tooltipVisible &&
          !this.overlayRef &&
          (!!this.text || !!this.template)
        ) {
          this.overlayRef = this.overlay.create({
            positionStrategy: this.overlay
              .position()
              .flexibleConnectedTo(this.elementRef)
              .withPositions([
                {
                  panelClass: 'tooltip-location-top',
                  originX: 'center',
                  originY: 'top',
                  overlayX: 'center',
                  overlayY: 'bottom',
                },
              ]),
          });
    
          const tooltipRef: ComponentRef<ProTooltipComponent> =
            this.overlayRef.attach(new ComponentPortal(ProTooltipComponent));
          tooltipRef.instance.text = this.text;
    
          this.tooltipVisible = true;
        }
      }
    
      private hideTooltip() {
        if (this.tooltipVisible && this.overlayRef) {
          this.overlayRef.detach();
          if (this.overlayRef) {
            this.overlayRef.dispose();
            this.overlayRef = null;
          }
          this.tooltipVisible = false;
        }
      }
    }
    

    Stackblitz Demo