'How to close a mat custom overlay on clicking outside when there is no backdrop?

I have created a dropdown panel using custom overlay from material, I need that if I set the property hasBackdrop: false, overlay should close if I click outside of dropdown panel just like mat autocomplete. I have written _getOutsideClickStream() to get outside click events. But panel gets open on click of trigger lets say a button and gets detached at that time only.

HTML

 <button #trigger>
 settings
 </button>
 <my-dropdown-panel #dropdownPanel  [origin]="trigger">some content</my-dropdown-panel>
@Component({
  selector: 'my-dropdown-panel',
  templateUrl: './dropdown-panel.component.html',
  encapsulation: ViewEncapsulation.None
})
export class DropdownPanelComponent implements TriggerTarget, OnChanges {
  @Input() origin: CdkOverlayOrigin;
  @Input() opened: boolean;
  @Input() hasBackdrop = true;
  @Output() openedChange: EventEmitter<boolean> = new EventEmitter<boolean>();
  @Input() type = DropdownType.Dropdown;
  @Input() xPosition = 'center';
  @Input() yPosition = 'bottom';
  className = 'my-dropdown-panel-container';
  @Input() closeIcon: boolean;
  private _overlayRef: OverlayRef;
  private _portal: TemplatePortal<any>;
  @ViewChild(TemplateRef, {static: false}) content: TemplateRef<any>;
  @Output() toggle = new EventEmitter<boolean>();
  clickoutHandler: Function;
    /** Whether the element is inside of a ShadowRoot component. */
    private _isInsideShadowRoot: boolean;
  constructor(private _overlay: Overlay,
              private _viewContainerRef: ViewContainerRef,
              private _dir: Directionality,
              private _zone: NgZone,
              private _eref: ElementRef,
              @Inject(DOCUMENT) private _document: any,
              private _element: ElementRef<HTMLInputElement>) {
    super();
    this._dir.change.subscribe(() => {
      // detach overlay to create new overlay with updated value;
      if (this._overlayRef) {
        this._overlayRef.detach();
        this._overlayRef = null;
      }
    })
  }
 ngOnChanges(changes: SimpleChanges): void {
  if (changes &&
      changes.hasBackdrop &&
      changes.hasBackdrop.previousValue !== changes.hasBackdrop.currentValue) {
    if (this._overlayRef) {
      this._overlayRef.detach();
      this._overlayRef = null;
    }
  }
 }
  open() {
    const origin = this._getOrigin();
    const overlay = this._getOverlayPosition();
    if (!this._overlayRef) {
      let positionStrategy: FlexibleConnectedPositionStrategy;
      positionStrategy = this._overlay.position()
        .flexibleConnectedTo(this.origin.elementRef)
        .withPositions([
          { ...origin.main, ...overlay.main },
          {...origin.fallback, ...overlay.fallback}
        ]);
      this.onPositionChanged({
        connectionPair: {
          overlayX: overlay.main.overlayX,
          overlayY: overlay.main.overlayY
        }
      });

      positionStrategy.positionChanges.subscribe(change => {
        this.onPositionChanged(change);
      });

      this._overlayRef = this._overlay.create({
        hasBackdrop: this.hasBackdrop,
        backdropClass: 'my-dropdown-panel-wrapper',
        positionStrategy,
        direction: this._dir.value
      });

      if (this._isInsideShadowRoot == null) {
        this._isInsideShadowRoot = !!_getShadowRoot(this._element.nativeElement);
      }

      this._overlayRef.backdropClick().subscribe(() => this.close());
      this._getOutsideClickStream().subscribe(()=>{
        this.close();
      });
      this._portal = new TemplatePortal(this.content, this._viewContainerRef);
    }



    if (!this._overlayRef.hasAttached()) {
      this._overlayRef.attach(this._portal);
      this.opened = true;
      this.toggle.emit(true);
    } else {
      this.close();
      this.toggle.emit(false);
    }
  };

  private _getOutsideClickStream(): Observable<any> {
    return merge(
               fromEvent(this._document, 'click') as Observable<MouseEvent>,
               fromEvent(this._document, 'touchend') as Observable<TouchEvent>)
        .pipe(filter(event => {
          // If we're in the Shadow DOM, the event target will be the shadow root, so we have to
          // fall back to check the first element in the path of the click event.
          const clickTarget =
              (this._isInsideShadowRoot && event.composedPath ? event.composedPath()[0] :
                                                                event.target) as HTMLElement;
          return this._overlayRef.hasAttached() && clickTarget !== this._element.nativeElement &&
              (!!this._overlayRef && !this._overlayRef.overlayElement.contains(clickTarget));
        }));
  }


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source