import { Directive , Output , EventEmitter , OnInit , OnDestroy , NgZone , Self } from '@angular/core' ; import { Subscription } from 'rxjs' ; import { CdkConnectedOverlay , ScrollDispatcher } from '@angular/cdk/overlay' ; @ Directive ( { selector : '[nglOverlayScrolledOutsideView]' } ) export class NglOverlaynglOverlayScrolledOutsideViewDirective implements OnInit , OnDestroy { @ Output ( 'nglOverlayScrolledOutsideView' ) overlayOutside : EventEmitter < void > = new EventEmitter ( ) ; private subscription : Subscription ; constructor ( @ Self ( ) private cdkOverlay , private ngZone , private scrollDispatcher ) { } ngOnInit ( ) { const elementRef = this . cdkOverlay . origin . elementRef ; const scrollableAncestors = this . scrollDispatcher . getAncestorScrollContainers ( elementRef ) . map ( container => container . getElementRef ( ) ) ; if ( ! scrollableAncestors || ! scrollableAncestors . length ) return ; this . subscription = this . cdkOverlay . positionChange . subscribe ( ( ) => { const bounds = elementRef . nativeElement . getBoundingClientRect ( ) ; for ( let i = 0 , n = scrollableAncestors . length ; i < n ; i ++ ) { const ancestorsBounds = scrollableAncestors [ i ] . nativeElement . getBoundingClientRect ( ) ; if ( isElementOutside ( bounds , ancestorsBounds ) ) { this . ngZone . run ( ( ) => this . overlayOutside . emit ( ) ) ; return ; } } } ) ; } ngOnDestroy ( ) { if ( this . subscription ) { this . subscription . unsubscribe ( ) ; this . subscription = null ; } } } param element param container returns export function isElementOutside ( element , container ) { return ( element . bottom < container . top || element . top > container . bottom || element . right < container . left || element . left > container . right ) ; }