license import { Platform } from '@angular/cdk/platform' ; import { ElementRef , Injectable , NgZone , OnDestroy , Optional , SkipSelf , } from '@angular/core' ; import { fromEvent , of as observableOf , Subject , Subscription , Observable , Observer } from 'rxjs' ; import { auditTime , filter } from 'rxjs/operators' ; import { CdkScrollable } from './scrollable' ; /** Time in ms to throttle the scrolling events by default. */ export const DEFAULT_SCROLL_TIME = 20 ; /**
 * Service contained all registered Scrollable references and emits an event when any one of the
 * Scrollable references emit a scrolled event.
 */ @ Injectable ( { providedIn : 'root' } ) export class ScrollDispatcher implements OnDestroy { constructor ( private _ngZone , private _platform ) { } /** Subject for notifying that a registered scrollable reference element has been scrolled. */ private _scrolled = new Subject < CdkScrollable | void > ( ) ; /** Keeps track of the global `scroll` and `resize` subscriptions. */ _globalSubscription : Subscription | null = null ; /** Keeps track of the amount of subscriptions to `scrolled`. Used for cleaning up afterwards. */ private _scrolledCount = 0 ; /**
     * Map of all the scrollable references that are registered with the service and their
     * scroll event subscriptions.
     */ scrollContainers : Map < CdkScrollable , Subscription > = new Map ( ) ; param scrollable register ( scrollable ) { if ( ! this . scrollContainers . has ( scrollable ) ) { this . scrollContainers . set ( scrollable , scrollable . elementScrolled ( ) . subscribe ( ( ) => this . _scrolled . next ( scrollable ) ) ) ; } } param scrollable deregister ( scrollable ) { const scrollableReference = this . scrollContainers . get ( scrollable ) ; if ( scrollableReference ) { scrollableReference . unsubscribe ( ) ; this . scrollContainers . delete ( scrollable ) ; } } /**
     * Returns an observable that emits an event whenever any of the registered Scrollable
     * references (or window, document, or body) fire a scrolled event. Can provide a time in ms
     * to override the default "throttle" time.
     *
     * **Note:** in order to avoid hitting change detection for every scroll event,
     * all of the events emitted from this stream will be run outside the Angular zone.
     * If you need to update any data bindings as a result of a scroll event, you have
     * to run the callback using `NgZone.run`.
     */ scrolled ( auditTimeInMs = DEFAULT_SCROLL_TIME ) { if ( ! this . _platform . isBrowser ) { return observableOf < void > ( ) ; } return Observable . create ( ( observer ) => { if ( ! this . _globalSubscription ) { this . _addGlobalListener ( ) ; } const subscription = auditTimeInMs > 0 ? this . _scrolled . pipe ( auditTime ( auditTimeInMs ) ) . subscribe ( observer ) : this . _scrolled . subscribe ( observer ) ; this . _scrolledCount ++ ; return ( ) => { subscription . unsubscribe ( ) ; this . _scrolledCount -- ; if ( ! this . _scrolledCount ) { this . _removeGlobalListener ( ) ; } } ; } ) ; } ngOnDestroy ( ) { this . _removeGlobalListener ( ) ; this . scrollContainers . forEach ( ( _ , container ) => this . deregister ( container ) ) ; this . _scrolled . complete ( ) ; } param elementRef param auditTimeInMs ancestorScrolled ( elementRef , auditTimeInMs ? ) { const ancestors = this . getAncestorScrollContainers ( elementRef ) ; return this . scrolled ( auditTimeInMs ) . pipe ( filter ( target => { return ! target || ancestors . indexOf ( target ) > - 1 ; } ) ) ; } /** Returns all registered Scrollables that contain the provided element. */ getAncestorScrollContainers ( elementRef ) { const scrollingContainers = [ ] ; this . scrollContainers . forEach ( ( _subscription , scrollable ) => { if ( this . _scrollableContainsElement ( scrollable , elementRef ) ) { scrollingContainers . push ( scrollable ) ; } } ) ; return scrollingContainers ; } /** Returns true if the element is contained within the provided Scrollable. */ private _scrollableContainsElement ( scrollable , elementRef ) { let element = elementRef . nativeElement ; let scrollableElement = scrollable . getElementRef ( ) . nativeElement ; do { if ( element == scrollableElement ) { return true ; } } while ( element = element ! . parentElement ) ; return false ; } /** Sets up the global scroll listeners. */ private _addGlobalListener ( ) { this . _globalSubscription = this . _ngZone . runOutsideAngular ( ( ) => { return fromEvent ( window . document , 'scroll' ) . subscribe ( ( ) => this . _scrolled . next ( ) ) ; } ) ; } /** Cleans up the global scroll listener. */ private _removeGlobalListener ( ) { if ( this . _globalSubscription ) { this . _globalSubscription . unsubscribe ( ) ; this . _globalSubscription = null ; } } } docs-private deprecated breaking-change export function SCROLL_DISPATCHER_PROVIDER_FACTORY ( parentDispatcher , ngZone , platform ) { return parentDispatcher || new ScrollDispatcher ( ngZone , platform ) ; } docs-private deprecated breaking-change export const SCROLL_DISPATCHER_PROVIDER = { provide : ScrollDispatcher , deps : [ [ new Optional ( ) , new SkipSelf ( ) , ScrollDispatcher ] , NgZone , Platform ] , useFactory : SCROLL_DISPATCHER_PROVIDER_FACTORY } ;