import { AfterViewInit , Directive , ElementRef , Input , Output , EventEmitter } from '@angular/core' ; import { Observable , Subscription , fromEvent , of } from 'rxjs' ; import { filter , map , pairwise , startWith } from 'rxjs/operators' ; import { ScrollPosition } from './scroll-position' ; @ Directive ( { selector : '[notaddInfiniteScrollable]' } ) export class NotaddInfiniteScrollableDirective implements AfterViewInit { readonly defaultScrollPosition : ScrollPosition ; private scrollEvent : Observable < UIEvent > ; private userScrolledDown : Observable < ScrollPosition > ; private emitOnScroll : Observable < ScrollPosition > ; @ Input ( ) public scrollPercent : number ; @ Input ( ) public immediateEmit : boolean ; @ Output ( ) public scrollComplete : EventEmitter < void > ; constructor ( private elementRef ) { this . defaultScrollPosition = { scrollHeight : 0 , scrollTop : 0 , clientHeight : 0 } ; this . scrollPercent = 70 ; this . immediateEmit = false ; this . scrollComplete = new EventEmitter < void > ( ) ; } ngAfterViewInit ( ) { this . registerScrollEvent ( ) ; this . streamScrollEvents ( ) ; this . emitScrollCompleteEvent ( ) ; } private registerScrollEvent ( ) { this . scrollEvent = fromEvent ( this . elementRef . nativeElement , 'scroll' ) ; } private streamScrollEvents ( ) { this . userScrolledDown = this . scrollEvent . pipe ( map ( ( { target } ) : ScrollPosition => ( { scrollHeight : target . scrollHeight , scrollTop : target . scrollTop , clientHeight : target . clientHeight } ) ) , pairwise ( ) , filter ( positions => this . isUserScrollingDown ( positions ) && this . isScrollExpectedPercent ( positions [ 1 ] ) ) , map ( positions => positions [ 1 ] ) ) ; } private emitScrollCompleteEvent ( ) { this . emitOnScroll = this . userScrolledDown ; if ( this . immediateEmit ) { this . emitOnScroll = this . emitOnScroll . pipe ( startWith ( this . defaultScrollPosition ) ) ; } this . emitOnScroll . subscribe ( ( ) => this . scrollComplete . emit ( ) ) ; } private isUserScrollingDown = ( positions ) : boolean => { return positions [ 0 ] . scrollTop < positions [ 1 ] . scrollTop ; } ; private isScrollExpectedPercent = ( position ) : boolean => { return ( ( position . scrollTop + position . clientHeight ) / position . scrollHeight ) > ( this . scrollPercent / 100 ) ; } ; }