2019-08-02 14:49:25 +02:00
|
|
|
import { distinctUntilChanged, filter, map, share, startWith, tap, throttleTime } from 'rxjs/operators'
|
|
|
|
import { AfterContentChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'
|
|
|
|
import { fromEvent, Observable, Subscription } from 'rxjs'
|
2018-02-13 14:11:05 +01:00
|
|
|
|
|
|
|
@Directive({
|
|
|
|
selector: '[myInfiniteScroller]'
|
|
|
|
})
|
2019-08-02 14:49:25 +02:00
|
|
|
export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterContentChecked {
|
2018-02-13 14:11:05 +01:00
|
|
|
@Input() percentLimit = 70
|
2018-08-24 10:31:56 +02:00
|
|
|
@Input() autoInit = false
|
2019-03-14 14:05:36 +01:00
|
|
|
@Input() onItself = false
|
2019-08-02 14:49:25 +02:00
|
|
|
@Input() dataObservable: Observable<any[]>
|
2018-02-13 14:11:05 +01:00
|
|
|
|
|
|
|
@Output() nearOfBottom = new EventEmitter<void>()
|
|
|
|
|
|
|
|
private decimalLimit = 0
|
|
|
|
private lastCurrentBottom = -1
|
2018-03-19 18:00:31 +01:00
|
|
|
private scrollDownSub: Subscription
|
2019-03-14 14:05:36 +01:00
|
|
|
private container: HTMLElement
|
2018-02-13 14:11:05 +01:00
|
|
|
|
2019-08-02 14:49:25 +02:00
|
|
|
private checkScroll = false
|
|
|
|
|
2019-03-14 14:05:36 +01:00
|
|
|
constructor (private el: ElementRef) {
|
2018-02-13 14:11:05 +01:00
|
|
|
this.decimalLimit = this.percentLimit / 100
|
|
|
|
}
|
|
|
|
|
2019-08-02 14:49:25 +02:00
|
|
|
ngAfterContentChecked () {
|
|
|
|
if (this.checkScroll) {
|
|
|
|
this.checkScroll = false
|
|
|
|
|
|
|
|
console.log('Checking if the initial state has a scroll.')
|
|
|
|
|
|
|
|
if (this.hasScroll() === false) this.nearOfBottom.emit()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-13 14:11:05 +01:00
|
|
|
ngOnInit () {
|
2018-08-24 10:31:56 +02:00
|
|
|
if (this.autoInit === true) return this.initialize()
|
2018-02-13 14:11:05 +01:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:31 +01:00
|
|
|
ngOnDestroy () {
|
|
|
|
if (this.scrollDownSub) this.scrollDownSub.unsubscribe()
|
|
|
|
}
|
|
|
|
|
2018-02-13 14:11:05 +01:00
|
|
|
initialize () {
|
2019-08-02 14:49:25 +02:00
|
|
|
this.container = this.onItself
|
|
|
|
? this.el.nativeElement
|
|
|
|
: document.documentElement
|
2019-03-14 14:05:36 +01:00
|
|
|
|
2018-03-08 10:46:12 +01:00
|
|
|
// Emit the last value
|
2018-03-09 09:21:34 +01:00
|
|
|
const throttleOptions = { leading: true, trailing: true }
|
2018-03-08 10:46:12 +01:00
|
|
|
|
2019-08-02 14:49:25 +02:00
|
|
|
const scrollableElement = this.onItself ? this.container : window
|
|
|
|
const scrollObservable = fromEvent(scrollableElement, 'scroll')
|
2018-05-15 11:55:51 +02:00
|
|
|
.pipe(
|
2019-07-25 16:23:44 +02:00
|
|
|
startWith(null as string), // FIXME: typings
|
2018-05-15 11:55:51 +02:00
|
|
|
throttleTime(200, undefined, throttleOptions),
|
2019-03-14 14:05:36 +01:00
|
|
|
map(() => this.getScrollInfo()),
|
2018-05-15 11:55:51 +02:00
|
|
|
distinctUntilChanged((o1, o2) => o1.current === o2.current),
|
|
|
|
share()
|
|
|
|
)
|
2018-02-13 14:11:05 +01:00
|
|
|
|
|
|
|
// Scroll Down
|
2018-03-19 18:00:31 +01:00
|
|
|
this.scrollDownSub = scrollObservable
|
2018-05-15 11:55:51 +02:00
|
|
|
.pipe(
|
2019-08-02 14:49:25 +02:00
|
|
|
filter(({ current }) => this.isScrollingDown(current)),
|
|
|
|
filter(({ current, maximumScroll }) => (current / maximumScroll) > this.decimalLimit)
|
2018-05-15 11:55:51 +02:00
|
|
|
)
|
2018-02-13 14:11:05 +01:00
|
|
|
.subscribe(() => this.nearOfBottom.emit())
|
2019-08-02 14:49:25 +02:00
|
|
|
|
|
|
|
if (this.dataObservable) {
|
|
|
|
this.dataObservable
|
|
|
|
.pipe(filter(d => d.length !== 0))
|
|
|
|
.subscribe(() => this.checkScroll = true)
|
|
|
|
}
|
2018-03-08 10:46:12 +01:00
|
|
|
}
|
2019-03-14 14:05:36 +01:00
|
|
|
|
|
|
|
private getScrollInfo () {
|
2019-08-02 14:49:25 +02:00
|
|
|
return { current: this.container.scrollTop, maximumScroll: this.getMaximumScroll() }
|
|
|
|
}
|
|
|
|
|
|
|
|
private getMaximumScroll () {
|
|
|
|
return this.container.scrollHeight - window.innerHeight
|
|
|
|
}
|
|
|
|
|
|
|
|
private hasScroll () {
|
|
|
|
return this.getMaximumScroll() > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
private isScrollingDown (current: number) {
|
|
|
|
const result = this.lastCurrentBottom < current
|
2019-03-14 14:05:36 +01:00
|
|
|
|
2019-08-02 14:49:25 +02:00
|
|
|
this.lastCurrentBottom = current
|
|
|
|
return result
|
2019-03-14 14:05:36 +01:00
|
|
|
}
|
2018-02-13 14:11:05 +01:00
|
|
|
}
|