52

I have this element that I'm referencing by Id:

    let infiniteScrollElement = document.getElementById('th-infinite-scroll-tracker');

I need to listen when the browser is has reached the bottom of the element.

How to achieve this?

kian
  • 1,449
  • 2
  • 13
  • 21
CommonSenseCode
  • 23,522
  • 33
  • 131
  • 186

5 Answers5

95

You could check whether the user has scrolled to the bottom or not in the below way...

Html file

<div (scroll)="onScroll($event)">
    ...
    ...
</div>

typescript file

onScroll(event: any) {
    // visible height + pixel scrolled >= total height 
    if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight) {
      console.log("End");
    }
}
Julien
  • 2,616
  • 1
  • 30
  • 43
Narottam Goyal
  • 3,534
  • 27
  • 26
  • 3
    Had to change `===` to `>=` since the equation on left side is bigger when scrolled completely to the bottom of the element. – user2340939 May 22 '18 at 17:03
  • 5
    I didn't need the `HostListener` just the `(scroll)='onScroll($event)'`. Super simple. – Steve Dec 05 '18 at 16:01
  • it did't need HostListener simple function will also work make sure you use isPlatformBrowser with it also HostListener scroll event continuously refreshes the whole Angular object which makes whole application heavy. don't use scroll event with HostListener. – Vikas Kandari Aug 20 '19 at 05:42
  • @NarottamGoyal when i reach to the end i make a service call and append the data to the table, but the scroller remains at the end and it makes a service call again and again. can you help on this. – AkRoy Oct 15 '19 at 11:29
  • 1
    Fires multiple times for me which was a deal breaker for me. Had to use AWolf's solution – yakya Jun 16 '20 at 18:55
  • Fits perfect for my requirement thank you – Geek Apr 15 '21 at 13:00
  • 2
    I was still having problems with this solution, turns out that for some cases the scrollTop was about .25 lower than the necessary to add up to total height. My solution was simply to round up like this: ```event.target.offsetHeight + Math.ceil(event.target.scrollTop) >= event.target.scrollHeight``` – mvoelcker Nov 29 '21 at 17:22
  • The event is not firing... is there anything else that needs to be done? – Awybin Feb 04 '22 at 20:12
  • For me I needed to add `target.offsetTop` to `offsetHeight` and `scrollTop`, because if not, the `scrollHeight` is never reached. I think that is needed, when you have a parent element, that has `padding-top` – dewey Mar 08 '23 at 11:28
52

I think that all you want to do is detect the position of scroll.

@HostListener("window:scroll", ["$event"])
onWindowScroll() {
//In chrome and some browser scroll is given to body tag
let pos = (document.documentElement.scrollTop || document.body.scrollTop) + document.documentElement.offsetHeight;
let max = document.documentElement.scrollHeight;
// pos/max will give you the distance between scroll bottom and and bottom of screen in percentage.
 if(pos == max )   {
 //Do your action here
 }
}

Also don't forget to import HostListener from @angular/core.

CommonSenseCode
  • 23,522
  • 33
  • 131
  • 186
YASH DAVE
  • 1,066
  • 12
  • 25
21

You could do this with an observable that's tracking the scroll event of your container.

Or you could create a host listener for your component that's listening for the scroll event. Please have a look at this SO question. (I haven't tested it with a host listener but that should work.)

Add the following code to your component for observable approach (I've copied some of the code from this blog post. ):

  ngOnInit() {
    /*
     * Create the observable from an event, in this case, the window scroll event
     * then map each event so we can get a new value from it
     * i.e. over time, we are just dealing with a collection:
     * (map [e1, e2, e3, ...]) -> [t1, t2, t3, ...]
     */
    let tracker = document.getElementById('th-infinite-scroll-tracker');

    let windowYOffsetObservable = Observable.fromEvent(tracker, 'scroll').map(() => {
      // I don't actually care about the event, I just need to get the window offset (scroll position)
      return tracker.scrollTop;
    });

    // subscribe to our Observable so that for each new item, our callback runs
    // this is our event handler
    let scrollSubscription = windowYOffsetObservable.subscribe((scrollPos) => {
      let limit = tracker.scrollHeight - tracker.clientHeight;
      console.log(scrollPos, limit);
      if (scrollPos === limit) {
        alert('end reached');
      }
    });
  }

Update

Another way and probably the best would be to create a directive for your tracking logic. Then you can easily use HostListener to bind to the scroll event.

Typescript code:

import {
  Directive, HostListener
}
from '@angular/core';

@Directive({
  selector: '[scrollTracker]'
})
export class ScrollTrackerDirective {
  @HostListener('scroll', ['$event']);

  onScroll(event) {
    // do tracking
    // console.log('scrolled', event.target.scrollTop);
    // Listen to click events in the component
    let tracker = event.target;

    let limit = tracker.scrollHeight - tracker.clientHeight;
    console.log(event.target.scrollTop, limit);
    if (event.target.scrollTop === limit) {
      alert('end reached');
    }
  }

  constructor() {}
}

Markup in your component (add your directive)

<div id="th-infinite-scroll-tracker" style="overflow-y:scroll; height: 500px;" scrollTracker>
  .... your container with scrollbar ...
</div>
Community
  • 1
  • 1
AWolf
  • 8,770
  • 5
  • 33
  • 39
  • 4
    [Here](https://stackblitz.com/edit/angular-5-scrolltracker) is the demo of the host listener directive so you can see the listener in action. – AWolf Mar 08 '18 at 08:50
  • The scroll event is not working for me, I'm using Angular 6, If I change this with `window:scroll`, It works. – Rohit Khatri Dec 22 '18 at 12:05
0

I am literally working on this now and found that this is the most versatile use case for "me."

As YASH DAVE mentioned, using a Host Listener is your best bet for an Angular 'onScroll' implementation. However, 'Window: Scroll' didn't work for my use case (a table injected within a Dashboard). So I had luck doing this


@HostListener('scroll', ['$event.target'])
 onScroll(elem){
  if(( elem.offsetHeight + elem.scrollTop) >=  elem.scrollHeight) {
     console.log("It's Lit");
  }
}'

CSS:

:host {
     display: block;
     max-height: 700px;
     width: 100%;
     overflow-y: scroll;
     scroll-behavior: auto;
 }

Explanation:

  • Basically the generic "scroll" listens for the scroll event occurring on the host element.
  • passes the element reference that triggered the scroll event via $event.target to my onScroll(elem) method
  • Then I take the element reference and determine if the offsetHeight and ScrollTop is greater than the scrollHeight (yes this is different then how others have implemented this) reference: mVChr's Implementation
  • Then boom, enjoy.
  • Note: If you just need a, and I can't stress this enough, simple solution just add a (scroll) event listener on the particular element

Additionally, review This for an insight on offsetHeight, clientHeight, scrollHeight.

0

Try this, it will work

@HostListener('body:scroll', ['$event'])

Dilraj Singh
  • 951
  • 10
  • 12