Search code examples
angularangular-material

Material CDK attaching backdrop to already opened overlay


Is it possible to attach backdrop to overlay that is opened? I'm looking for something opposite to .detachBackdrop() function.

In overlay config I want to keep hasBackdrop negative and add it later when user starts to interact with overlay attached component.

Thanks for any idea🙂


Solution

  • I was able to add attachBackdrop() function by extending default OverlayRef class and creating it based on private _attachBackdrop() method:

    import { OverlayConfig, OverlayKeyboardDispatcher, OverlayOutsideClickDispatcher, OverlayRef } from "@angular/cdk/overlay";
    import { PortalOutlet } from "@angular/cdk/portal";
    import { EnvironmentInjector, Injectable, NgZone } from "@angular/core";
    import { Location } from "@angular/common";
    
    export type ImmutableObject<T> = {
        readonly [P in keyof T]: T[P];
    };
    
    export class DynamicOverlayRef extends OverlayRef {
    
        constructor(
            _portalOutlet: PortalOutlet,
            _host: HTMLElement,
            _pane: HTMLElement,
            _config: ImmutableObject<OverlayConfig>,
            _ngZone: NgZone,
            _keyboardDispatcher: OverlayKeyboardDispatcher,
            _document: Document,
            _location: Location,
            _outsideClickDispatcher: OverlayOutsideClickDispatcher,
            _animationsDisabled = false,
            _injector: EnvironmentInjector,
          ) {
            super( 
                _portalOutlet,
                _host,
                _pane,
                _config,
                _ngZone,
                _keyboardDispatcher,
                _document,
                _location,
                _outsideClickDispatcher,
                // _animationsDisabled,
                // _injector
              );
        }
    
        public attachBackdrop() {
            const showingClass = 'cdk-overlay-backdrop-showing';
        
            this["_backdropElement"] = this["_document"].createElement('div');
            this["_backdropElement"].classList.add('cdk-overlay-backdrop');
        
            if (this["_animationsDisabled"]) {
              this["_backdropElement"].classList.add('cdk-overlay-backdrop-noop-animation');
            }
        
            if (this["_config.backdropClass"]) {
              this["_toggleClasses"](this["_backdropElement"], this["_config.backdropClass"], true);
            }
        
            // Insert the backdrop before the pane in the DOM order,
            // in order to handle stacked overlays properly.
            this["_host"].parentElement!.insertBefore(this["_backdropElement"], this["_host"]);
        
            // Forward backdrop clicks such that the consumer of the overlay can perform whatever
            // action desired when such a click occurs (usually closing the overlay).
            this["_backdropElement"].addEventListener('click', this["_backdropClickHandler"]);
        
            // Add class to fade-in the backdrop after one frame.
            if (!this["_animationsDisabled"] && typeof requestAnimationFrame !== 'undefined') {
              this["_ngZone"].runOutsideAngular(() => {
                requestAnimationFrame(() => {
                  if (this["_backdropElement"]) {
                    this["_backdropElement"].classList.add(showingClass);
                  }
                });
              });
            } else {
                this["_backdropElement"].classList.add(showingClass);
            }
          }
      
    }
    

    It creates problem with adding custom backdrop class using backdropClass property during overlay configuration, but it could by easily solved e.g. adding parameter to this attachBackdrop() method.

    If there's no need to add custom code to attaching function it could be shortened to:

    public attachBackdrop() {
        this["_attachBackdrop"]();
    }