import { Directive , ElementRef , EventEmitter , Input , NgZone , Output } from '@angular/core' ; import { Content , ScrollEvent } from '../content/content' ; import { DomController } from '../../platform/dom-controller' ; import { assert } from '../../util/util' ; name description usage Component Component demo @ Directive ( { selector : 'ion-infinite-scroll' } ) export class InfiniteScroll { _lastCheck : number = 0 ; _highestY : number = 0 ; _scLsn : any ; _thr : string = '15%' ; _thrPx : number = 0 ; _thrPc : number = 0.15 ; _position : string = POSITION_BOTTOM ; _init : boolean = false ; internal state : string = STATE_ENABLED ; input @ Input ( ) get threshold ( ) : string { return this . _thr ; } set threshold ( val ) { this . _thr = val ; if ( val . indexOf ( '%' ) > - 1 ) { this . _thrPx = 0 ; this . _thrPc = ( parseFloat ( val ) / 100 ) ; } else { this . _thrPx = parseFloat ( val ) ; this . _thrPc = 0 ; } } input @ Input ( ) set enabled ( shouldEnable ) { this . enable ( shouldEnable ) ; } input @ Input ( ) get position ( ) : string { return this . _position ; } set position ( val ) { if ( val === POSITION_TOP || val === POSITION_BOTTOM ) { this . _position = val ; } else { console . error ( `Invalid value for ion-infinite-scroll's position input. Its value should be '${ POSITION_BOTTOM }' or '${ POSITION_TOP }'.` ) ; } } output @ Output ( ) ionInfinite : EventEmitter < InfiniteScroll > = new EventEmitter < InfiniteScroll > ( ) ; constructor ( private _content , private _zone , private _elementRef , private _dom ) { _content . setElementClass ( 'has-infinite-scroll' , true ) ; } _onScroll ( ev ) { if ( this . state === STATE_LOADING || this . state === STATE_DISABLED ) { return 1 ; } if ( this . _lastCheck + 32 > ev . timeStamp ) { return 2 ; } this . _lastCheck = ev . timeStamp ; const infiniteHeight = this . _elementRef . nativeElement . scrollHeight ; if ( ! infiniteHeight ) { return 3 ; } const d = this . _content . getContentDimensions ( ) ; const height = d . contentHeight ; const threshold = this . _thrPc ? ( height * this . _thrPc ) : this . _thrPx ; let distanceFromInfinite ; if ( this . _position === POSITION_BOTTOM ) { distanceFromInfinite = d . scrollHeight - infiniteHeight - d . scrollTop - height - threshold ; } else { assert ( this . _position === POSITION_TOP , '_position should be top' ) ; distanceFromInfinite = d . scrollTop - infiniteHeight - threshold ; } if ( distanceFromInfinite < 0 ) { this . _dom . write ( ( ) => { this . _zone . run ( ( ) => { if ( this . state !== STATE_LOADING && this . state !== STATE_DISABLED ) { this . state = STATE_LOADING ; this . ionInfinite . emit ( this ) ; } } ) ; } ) ; return 5 ; } return 6 ; } /**
     * Call `complete()` within the `infinite` output event handler when
     * your async operation has completed. For example, the `loading`
     * state is while the app is performing an asynchronous operation,
     * such as receiving more data from an AJAX request to add more items
     * to a data list. Once the data has been received and UI updated, you
     * then call this method to signify that the loading has completed.
     * This method will change the infinite scroll's state from `loading`
     * to `enabled`.
     */ complete ( ) { if ( this . state !== STATE_LOADING ) { return ; } if ( this . _position === POSITION_BOTTOM ) { this . state = STATE_ENABLED ; return ; } assert ( this . _position === POSITION_TOP , 'position should be top' ) ; const prevDim = this . _content . getContentDimensions ( ) ; this . _dom . read ( ( ) => { const newDim = this . _content . getContentDimensions ( ) ; const newScrollTop = newDim . scrollHeight - ( prevDim . scrollHeight - prevDim . scrollTop ) ; this . _dom . write ( ( ) => { this . _content . scrollTop = newScrollTop ; this . state = STATE_ENABLED ; } ) ; } ) ; } /**
    * Pass a promise inside `waitFor()` within the `infinite` output event handler in order to
    * change state of infiniteScroll to "complete"
    */ waitFor ( action ) { const enable = this . complete . bind ( this ) ; action . then ( enable , enable ) ; } param { boolean } shouldEnable enable ( shouldEnable ) { this . state = ( shouldEnable ? STATE_ENABLED : STATE_DISABLED ) ; this . _setListeners ( shouldEnable ) ; } hidden _setListeners ( shouldListen ) { if ( this . _init ) { if ( shouldListen ) { if ( ! this . _scLsn ) { this . _scLsn = this . _content . ionScroll . subscribe ( this . _onScroll . bind ( this ) ) ; } } else { this . _scLsn && this . _scLsn . unsubscribe ( ) ; this . _scLsn = null ; } } } hidden ngAfterContentInit ( ) { this . _init = true ; this . _setListeners ( this . state !== STATE_DISABLED ) ; if ( this . _position === POSITION_TOP ) { this . _content . scrollDownOnLoad = true ; } } hidden ngOnDestroy ( ) { this . _setListeners ( false ) ; } } const STATE_ENABLED = 'enabled' ; const STATE_DISABLED = 'disabled' ; const STATE_LOADING = 'loading' ; const POSITION_TOP = 'top' ; const POSITION_BOTTOM = 'bottom' ;