-
-
Save danierdev/c8c891f52b10dbebeee1b52616372ad5 to your computer and use it in GitHub Desktop.
This is an Angular 2 infinite scroll directive. It is simple, easy to use and very CPU-efficient.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// USAGE: | |
// | |
// When you attach the infiniteScroll directive to an element, it will emit the infiniteScrollAction | |
// @Output() event every time the user has scrolled to the bottom of the element. Your loadMoreArticles | |
// function can make an HTTP call and append the results to the articles list, for example. In doing this, | |
// you effectively increase the height of the element and thus begin the process of the infiniteScroll directive | |
// again, over and over until the element height stops increasing. | |
// | |
// <div class="container" infiniteScroll (infiniteScrollAction)="loadMoreArticles()"> | |
// <div class="article" *ngFor="let article of articles"> | |
// ... | |
// </div> | |
// </div> | |
// | |
// <div class="container" infiniteScroll [infiniteScrollContext]="'self'" (infiniteScrollAction)="loadMoreArticles()"> | |
// <div class="article" *ngFor="let article of articles"> | |
// ... | |
// </div> | |
// </div> | |
import { Directive, Input, Output, EventEmitter, ElementRef, OnInit } from '@angular/core'; | |
import { Observable } from 'rxjs/Observable'; | |
import 'rxjs/add/observable/fromEvent'; | |
import 'rxjs/add/operator/debounceTime'; | |
import 'rxjs/add/operator/map'; | |
export interface Viewport { | |
width: number; | |
height: number; | |
} | |
// InfiniteScrollContext represents the context in which the directive will run. | |
// | |
// The default is 'document' and this will trigger your action when the end of | |
// your element has been reached relative to the documents scrollbar. | |
// | |
// If you use 'self', your action will be triggered when the end of your element | |
// has been reached relative to your elements own scrollbar. | |
export type InfiniteScrollContext = 'self' | 'document'; | |
@Directive({ | |
selector: '[infiniteScroll]', | |
}) | |
export class InfiniteScrollDirective implements OnInit { | |
private el: any; | |
private viewport: Viewport; | |
private scrollEvent$: Observable<any>; | |
@Input() infiniteScrollContext: InfiniteScrollContext = 'document'; | |
@Output() infiniteScrollAction: EventEmitter<any> = new EventEmitter(); | |
constructor(private element: ElementRef) { | |
this.el = element.nativeElement; | |
this.viewport = this.getViewport(window); | |
} | |
ngOnInit() { | |
if (this.infiniteScrollContext === 'self') { | |
this.scrollEvent$ = Observable.fromEvent(this.el, 'scroll').debounceTime(250); | |
this.scrollEvent$.subscribe((e: any) => { | |
if (e.target.scrollTop + e.target.offsetHeight >= e.target.scrollHeight) { | |
this.infiniteScrollAction.emit(null); | |
} | |
}); | |
} else if (this.infiniteScrollContext === 'document') { | |
this.scrollEvent$ = Observable.fromEvent(window.document, 'scroll').debounceTime(250); | |
this.scrollEvent$.subscribe(() => { | |
const rect = this.el.getBoundingClientRect(); | |
const elementTopRelativeToViewport = rect.top; | |
const elementTopRelativeToDocument = elementTopRelativeToViewport + this.win.pageYOffset; | |
const scrollableDistance = this.el.offsetHeight + elementTopRelativeToDocument; | |
const currentPos = window.pageYOffset + this.viewport.height; | |
if (currentPos > scrollableDistance) { | |
this.infiniteScrollAction.emit(null); | |
} | |
}); | |
} else { | |
throw new Error(`'infiniteScrollContext' contains invalid value ${this.infiniteScrollContext}. Only 'self' and 'document' are allowed.`); | |
} | |
} | |
private getViewport(win: Window): Viewport { | |
// This works for all browsers except IE8 and before | |
if (win.innerWidth != null) { | |
return { | |
width: win.innerWidth, | |
height: win.innerHeight | |
}; | |
} | |
// For IE (or any browser) in Standards mode | |
let d = win.document; | |
if (document.compatMode == 'CSS1Compat') { | |
return { | |
width: d.documentElement.clientWidth, | |
height: d.documentElement.clientHeight | |
}; | |
} | |
// For browsers in Quirks mode | |
return { | |
width: d.body.clientWidth, | |
height: d.body.clientHeight | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment