Skip to content

Instantly share code, notes, and snippets.

@lansana
Last active April 11, 2022 09:02
Show Gist options
  • Select an option

  • Save lansana/2c04abe668d1c97aded7e5ad9d0d74d9 to your computer and use it in GitHub Desktop.

Select an option

Save lansana/2c04abe668d1c97aded7e5ad9d0d74d9 to your computer and use it in GitHub Desktop.

Revisions

  1. lansana revised this gist Jul 11, 2017. 1 changed file with 39 additions and 54 deletions.
    93 changes: 39 additions & 54 deletions infinite-scroll.directive.ts
    Original file line number Diff line number Diff line change
    @@ -18,13 +18,16 @@
    // </div>
    // </div>

    import { Directive, Input, Output, EventEmitter, ElementRef, OnInit } from '@angular/core';


    import { Directive, Input, Output, EventEmitter, ElementRef, HostListener, 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 {
    h: number;
    w: number;
    width: number;
    height: number;
    }

    // InfiniteScrollContext represents the context in which the directive will run.
    @@ -40,70 +43,52 @@ export type InfiniteScrollContext = 'self' | 'document';
    selector: '[infiniteScroll]',
    })
    export class InfiniteScrollDirective implements OnInit {
    el: any;
    viewport: Viewport;
    canTriggerAction: boolean = true;
    private el: any;
    private viewport: Viewport;
    private scrollEvent$: Observable<any>;

    @Input() infiniteScrollContext: InfiniteScrollContext = 'document';
    @Output() infiniteScrollAction: EventEmitter<any> = new EventEmitter();
    @HostListener('scroll', ['$event']) onElementScroll() {
    if (this.infiniteScrollContext === 'self') {
    if (this.elementEndReachedInSelfScrollbarContext() && this.canTriggerAction) {
    this.triggerAction();
    }
    }
    }

    constructor(private element: ElementRef) {
    this.el = element.nativeElement;
    this.viewport = this.getViewport(window);
    }

    ngOnInit() {
    if (this.infiniteScrollContext === 'document') {
    document.addEventListener('scroll', () => {
    if (this.elementEndReachedInDocumentScrollbarContext(window, this.el) && this.canTriggerAction) {
    this.triggerAction();
    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.`);
    }
    }

    triggerAction() {
    this.canTriggerAction = false;
    this.infiniteScrollAction.emit(null);
    }

    elementEndReachedInSelfScrollbarContext(): boolean {
    if (this.el.scrollTop + this.el.offsetHeight >= this.el.scrollHeight) {
    this.canTriggerAction = true;
    return true;
    }

    return false;
    }

    elementEndReachedInDocumentScrollbarContext(win: Window, el: any): boolean {
    const rect = el.getBoundingClientRect();
    const elementTopRelativeToViewport = rect.top;
    const elementTopRelativeToDocument = elementTopRelativeToViewport + win.pageYOffset;
    const scrollableDistance = el.offsetHeight + elementTopRelativeToDocument;
    const currentPos = win.pageYOffset + this.viewport.h;

    if (currentPos > scrollableDistance) {
    this.canTriggerAction = true;
    return true;
    }

    return false;
    }

    private getViewport(win: Window): Viewport {
    // This works for all browsers except IE8 and before
    if (win.innerWidth != null) {
    return {
    w: win.innerWidth,
    h: win.innerHeight
    width: win.innerWidth,
    height: win.innerHeight
    };
    }

    @@ -112,15 +97,15 @@ export class InfiniteScrollDirective implements OnInit {

    if (document.compatMode == 'CSS1Compat') {
    return {
    w: d.documentElement.clientWidth,
    h: d.documentElement.clientHeight
    width: d.documentElement.clientWidth,
    height: d.documentElement.clientHeight
    };
    }

    // For browsers in Quirks mode
    return {
    w: d.body.clientWidth,
    h: d.body.clientHeight
    width: d.body.clientWidth,
    height: d.body.clientHeight
    };
    }
    }
    }
  2. lansana revised this gist Jun 1, 2017. 1 changed file with 7 additions and 5 deletions.
    12 changes: 7 additions & 5 deletions infinite-scroll.directive.ts
    Original file line number Diff line number Diff line change
    @@ -6,11 +6,6 @@
    // 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.
    //
    // There is a flag @Input() called 'infiniteScrollContext'. You can pass in two options: 'self', or 'document'.
    // The default selection is document, and this means that the directive will run in the context of the documents
    // scrollbar. If you use 'self', then the directive will run in the context of the respective elements scrollbar.
    // All other functionality stays the same.
    //
    // <div class="container" infiniteScroll (infiniteScrollAction)="loadMoreArticles()">
    // <div class="article" *ngFor="let article of articles">
    // ...
    @@ -32,6 +27,13 @@ export interface Viewport {
    w: 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({
  3. lansana revised this gist Jun 1, 2017. 1 changed file with 28 additions and 20 deletions.
    48 changes: 28 additions & 20 deletions infinite-scroll.directive.ts
    Original file line number Diff line number Diff line change
    @@ -45,7 +45,11 @@ export class InfiniteScrollDirective implements OnInit {
    @Input() infiniteScrollContext: InfiniteScrollContext = 'document';
    @Output() infiniteScrollAction: EventEmitter<any> = new EventEmitter();
    @HostListener('scroll', ['$event']) onElementScroll() {
    if (this.endReached(window, this.el)) this.trigger();
    if (this.infiniteScrollContext === 'self') {
    if (this.elementEndReachedInSelfScrollbarContext() && this.canTriggerAction) {
    this.triggerAction();
    }
    }
    }

    constructor(private element: ElementRef) {
    @@ -56,33 +60,37 @@ export class InfiniteScrollDirective implements OnInit {
    ngOnInit() {
    if (this.infiniteScrollContext === 'document') {
    document.addEventListener('scroll', () => {
    if (this.endReached(window, this.el) && this.canTriggerAction) this.trigger();
    if (this.elementEndReachedInDocumentScrollbarContext(window, this.el) && this.canTriggerAction) {
    this.triggerAction();
    }
    });
    }
    }

    trigger() {
    triggerAction() {
    this.canTriggerAction = false;
    this.infiniteScrollAction.emit(null);
    }

    endReached(win: Window, el: any): boolean {
    if (this.infiniteScrollContext === 'self') {
    if (this.el.scrollTop + this.el.offsetHeight >= this.el.scrollHeight) {
    this.canTriggerAction = true;
    return true;
    }
    } else if (this.infiniteScrollContext === 'document') {
    const rect = el.getBoundingClientRect();
    const elementTopRelativeToViewport = rect.top;
    const elementTopRelativeToDocument = elementTopRelativeToViewport + win.pageYOffset;
    const scrollableDistance = el.offsetHeight + elementTopRelativeToDocument;
    const currentPos = win.pageYOffset + this.viewport.h;

    if (currentPos > scrollableDistance) {
    this.canTriggerAction = true;
    return true;
    }
    elementEndReachedInSelfScrollbarContext(): boolean {
    if (this.el.scrollTop + this.el.offsetHeight >= this.el.scrollHeight) {
    this.canTriggerAction = true;
    return true;
    }

    return false;
    }

    elementEndReachedInDocumentScrollbarContext(win: Window, el: any): boolean {
    const rect = el.getBoundingClientRect();
    const elementTopRelativeToViewport = rect.top;
    const elementTopRelativeToDocument = elementTopRelativeToViewport + win.pageYOffset;
    const scrollableDistance = el.offsetHeight + elementTopRelativeToDocument;
    const currentPos = win.pageYOffset + this.viewport.h;

    if (currentPos > scrollableDistance) {
    this.canTriggerAction = true;
    return true;
    }

    return false;
  4. lansana revised this gist Jun 1, 2017. 1 changed file with 51 additions and 20 deletions.
    71 changes: 51 additions & 20 deletions infinite-scroll.directive.ts
    Original file line number Diff line number Diff line change
    @@ -6,52 +6,83 @@
    // 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.
    //
    // There is a flag @Input() called 'infiniteScrollContext'. You can pass in two options: 'self', or 'document'.
    // The default selection is document, and this means that the directive will run in the context of the documents
    // scrollbar. If you use 'self', then the directive will run in the context of the respective elements scrollbar.
    // All other functionality stays the same.
    //
    // <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, Output, EventEmitter, ElementRef } from '@angular/core';
    import { Directive, Input, Output, EventEmitter, ElementRef, HostListener, OnInit } from '@angular/core';

    interface Viewport {
    export interface Viewport {
    h: number;
    w: number;
    }

    export type InfiniteScrollContext = 'self' | 'document';

    @Directive({
    selector: '[infiniteScroll]',
    })
    export class InfiniteScrollDirective {
    export class InfiniteScrollDirective implements OnInit {
    el: any;
    viewport: Viewport;
    canTriggerAction: boolean = true;

    @Input() infiniteScrollContext: InfiniteScrollContext = 'document';
    @Output() infiniteScrollAction: EventEmitter<any> = new EventEmitter();
    @HostListener('scroll', ['$event']) onElementScroll() {
    if (this.endReached(window, this.el)) this.trigger();
    }

    constructor(private element: ElementRef) {
    const el = element.nativeElement;

    this.el = element.nativeElement;
    this.viewport = this.getViewport(window);
    }

    document.addEventListener('scroll', () => {
    if (this.elementEndReached(window, el) && this.canTriggerAction) {
    this.canTriggerAction = false;
    this.infiniteScrollAction.emit(null);
    }
    });
    ngOnInit() {
    if (this.infiniteScrollContext === 'document') {
    document.addEventListener('scroll', () => {
    if (this.endReached(window, this.el) && this.canTriggerAction) this.trigger();
    });
    }
    }

    elementEndReached(win: Window, el: any): boolean {
    const rect = el.getBoundingClientRect();
    const elementTopRelativeToViewport = rect.top;
    const elementTopRelativeToDocument = elementTopRelativeToViewport + win.pageYOffset;
    const scrollableDistance = el.offsetHeight + elementTopRelativeToDocument;
    const currentPos = win.pageYOffset + this.viewport.h;
    trigger() {
    this.canTriggerAction = false;
    this.infiniteScrollAction.emit(null);
    }

    endReached(win: Window, el: any): boolean {
    if (this.infiniteScrollContext === 'self') {
    if (this.el.scrollTop + this.el.offsetHeight >= this.el.scrollHeight) {
    this.canTriggerAction = true;
    return true;
    }
    } else if (this.infiniteScrollContext === 'document') {
    const rect = el.getBoundingClientRect();
    const elementTopRelativeToViewport = rect.top;
    const elementTopRelativeToDocument = elementTopRelativeToViewport + win.pageYOffset;
    const scrollableDistance = el.offsetHeight + elementTopRelativeToDocument;
    const currentPos = win.pageYOffset + this.viewport.h;

    if (currentPos > scrollableDistance) {
    this.canTriggerAction = true;
    return true;
    if (currentPos > scrollableDistance) {
    this.canTriggerAction = true;
    return true;
    }
    }

    return false;
  5. lansana revised this gist May 31, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion infinite-scroll.directive.ts
    Original file line number Diff line number Diff line change
    @@ -29,7 +29,7 @@ export class InfiniteScrollDirective {
    canTriggerAction: boolean = true;
    @Output() infiniteScrollAction: EventEmitter<any> = new EventEmitter();

    constructor(private element: ElementRef,) {
    constructor(private element: ElementRef) {
    const el = element.nativeElement;

    this.viewport = this.getViewport(window);
  6. lansana created this gist May 31, 2017.
    85 changes: 85 additions & 0 deletions infinite-scroll.directive.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,85 @@
    // 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>



    import { Directive, Output, EventEmitter, ElementRef } from '@angular/core';

    interface Viewport {
    h: number;
    w: number;
    }

    @Directive({
    selector: '[infiniteScroll]',
    })
    export class InfiniteScrollDirective {
    viewport: Viewport;
    canTriggerAction: boolean = true;
    @Output() infiniteScrollAction: EventEmitter<any> = new EventEmitter();

    constructor(private element: ElementRef,) {
    const el = element.nativeElement;

    this.viewport = this.getViewport(window);

    document.addEventListener('scroll', () => {
    if (this.elementEndReached(window, el) && this.canTriggerAction) {
    this.canTriggerAction = false;
    this.infiniteScrollAction.emit(null);
    }
    });
    }

    elementEndReached(win: Window, el: any): boolean {
    const rect = el.getBoundingClientRect();
    const elementTopRelativeToViewport = rect.top;
    const elementTopRelativeToDocument = elementTopRelativeToViewport + win.pageYOffset;
    const scrollableDistance = el.offsetHeight + elementTopRelativeToDocument;
    const currentPos = win.pageYOffset + this.viewport.h;

    if (currentPos > scrollableDistance) {
    this.canTriggerAction = true;
    return true;
    }

    return false;
    }

    private getViewport(win: Window): Viewport {
    // This works for all browsers except IE8 and before
    if (win.innerWidth != null) {
    return {
    w: win.innerWidth,
    h: win.innerHeight
    };
    }

    // For IE (or any browser) in Standards mode
    let d = win.document;

    if (document.compatMode == 'CSS1Compat') {
    return {
    w: d.documentElement.clientWidth,
    h: d.documentElement.clientHeight
    };
    }

    // For browsers in Quirks mode
    return {
    w: d.body.clientWidth,
    h: d.body.clientHeight
    };
    }
    }