Automatic scrolling, only if a user already scrolled the bottom of a page in Angular

In chat windows or similar growing lists, users often expect, that a page automatically scrolls to the latest item. That behavior obviously should get stopped, when a user scrolls through the list and is not viewing that latest item currently. Here is how to achieve that in Angular.

Automatic scrolling, only if a user already scrolled the bottom of a page in Angular

In chat windows or similar growing lists, users often expect, that a page automatically scrolls to the latest item. That behavior obviously should get stopped, when a user scrolls through the list and is not viewing that latest item currently. Here is how to achieve that in Angular.

If you are only interested in the final code, you can find the full sample at StackBlitz, to play around with it.

Here is a quick video, demonstrating, what we are trying to achieve:

First, we need a list of items, an HTML element to display these items in and a method to call for scrolling to the bottom automatically. An Angular component that contains all that can look like this:

<!-- app.component.html --> 
<div class="frame" #scrollframe>
  <div *ngFor="let item of items" class="item" #item>
    Item
  </div>
</div>
// app.component.ts
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit  {
  @ViewChild('scrollframe', {static: false}) scrollFrame: ElementRef;
  @ViewChildren('item') itemElements: QueryList<any>;
  
  private scrollContainer: any;
  private items = [];

  ngAfterViewInit() {
    this.scrollContainer = this.scrollFrame.nativeElement;  
    this.itemElements.changes.subscribe(_ => this.onItemElementsChanged());    

    // Add a new item every 2 seconds
    setInterval(() => {
      this.items.push({});
    }, 2000);
  }
  
  private onItemElementsChanged(): void {
    this.scrollToBottom();
  }

  private scrollToBottom(): void {
    this.scrollContainer.scroll({
      top: this.scrollContainer.scrollHeight,
      left: 0,
      behavior: 'smooth'
    });
  }
}

As you can see, we defined the container element in which we want to perform the scrolling operation. The code above lets the view automatically scroll down to the bottom, if the itemElements list changes.

Only scroll automatically, if user has already scrolled to the end

Usually, we only want to scroll automatically, if the user is already viewing the last element the bottom. Otherwise we should remain still. So we need a function to tell us, if the user's current scroll position is near the bottom of the screen.

private isUserNearBottom(): boolean {
  const threshold = 150;
  const position = this.scrollContainer.scrollTop + this.scrollContainer.offsetHeight;
  const height = this.scrollContainer.scrollHeight;
  return position > height - threshold;
}

Let's add a new isNearBottom variable, which we can set, whenever the user scrolled within the scrollContainer element. For this, we need to subscribe to the scroll event in the HTML element.

<div class="frame" (scroll)="scrolled($event)" #scrollframe>
  <!-- ... -->
</div>

Within the component, we need to react on this event and update the isNearBottom variable.

scrolled(event: any): void {
  this.isNearBottom = this.isUserNearBottom();
}

With this in place, we can update the onItemElementsChanged() method, to only scroll down, if the user's current scroll position is near bottom.

private onItemElementsChanged(): void {
  if (this.isNearBottom()) {
    this.scrollToBottom();
  }
}

What if I want to scroll a full window?

If you want to scroll the full window instead of the content of a single div, the scrolling code looks slightly different:

private scrollToBottom(): void {
    window.scroll({ // <- Scroll window instead of scrollContainer
      top: this.scrollContainer.scrollHeight,
      left: 0,
      behavior: 'smooth'
    });
  }

  private isUserNearBottom(): boolean {
    const threshold = 150;
    const position = window.scrollY + window.innerHeight; // <- Measure position differently
    const height = document.body.scrollHeight; // <- Measure height differently
    return position > height - threshold;
  }

  @HostListener('window:scroll', ['$event']) // <- Add scroll listener to window
  scrolled(event: any): void {
    this.isNearBottom = this.isUserNearBottom();
  }

Full example

You can find the full sample at StackBlitz, to play around with it.


☝️ Advertisement Block: I will buy myself a pizza every time I make enough money with these ads to do so. So please feed a hungry developer and consider disabling your Ad Blocker.