Skip to content

Instantly share code, notes, and snippets.

@lansana
Last active April 11, 2022 09:02
Show Gist options
  • Save lansana/2c04abe668d1c97aded7e5ad9d0d74d9 to your computer and use it in GitHub Desktop.
Save lansana/2c04abe668d1c97aded7e5ad9d0d74d9 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.
// 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