0

I am working on a project where I need to implement a virtual scrolling component in Angular without using any third-party libraries. I found this article about building a virtual scroll in React(https://dev.to/adamklein/build-your-own-virtual-scroll-part-i-11ib), but I am unable to understand how the itemsToDisplay array works in this context. It suppose to load items that fit within the viewport only once and there will not be a scroll bar to scroll after that.

enter image description here

I am new to angular and I'm not sure if there is something I'm missing. Can anyone help me with this issue. Thanks in advance.

The source is as follows.

virtual-scroller.component.ts

import {
  Component,
  Input,
  Output,
  EventEmitter,
  ViewChild,
  ElementRef,
  AfterViewInit,
} from '@angular/core';

@Component({
  selector: 'app-virtual-scroller',
  templateUrl: './virtual-scroller.component.html',
  styleUrls: ['./virtual-scroller.component.css'],
})
export class VirtualScrollerComponent implements AfterViewInit {
  @Input()
  items: any[] = [];
  @Output() scrollEnd = new EventEmitter();
  @ViewChild('container')
  container!: ElementRef;

  height = '100px';
  itemsToDisplay: any[] = [];

  ngAfterViewInit() {
    setTimeout(() => {
      this.loadItems();
    }, 0);
  }

  loadItems() {
    const viewportHeight = this.container.nativeElement.offsetHeight;
    const itemHeight = 50;
    const itemsPerViewport = Math.ceil(viewportHeight / itemHeight);
    this.itemsToDisplay = this.items.slice(0, itemsPerViewport);
  }

  onScroll(event: any) {
    const viewportHeight = this.container.nativeElement.offsetHeight;
    const scrollHeight = this.container.nativeElement.scrollHeight;
    const scrollTop = this.container.nativeElement.scrollTop;
    const itemHeight = 50;
    const itemsPerViewport = Math.ceil(viewportHeight / itemHeight);

    if (scrollTop + viewportHeight >= scrollHeight) {
      this.scrollEnd.emit();
    } else {
      const firstVisibleIndex = Math.floor(scrollTop / itemHeight);
      this.itemsToDisplay = this.items.slice(
        firstVisibleIndex,
        firstVisibleIndex + itemsPerViewport
      );
    }
  }
}

virtual-scroller.component.html

<div #container [style.height]="height" [style.overflow-y]="'scroll'" (scroll)="onScroll($event)">
    <div *ngFor="let item of itemsToDisplay">
      {{ item.name }}
    </div>
  </div>

** home.component.ts**

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

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
})
export class HomeComponent implements OnInit {
  ngOnInit(): void {}
  items: any[] = [
    { name: 'Gayan' },
    { name: 'Sahan' },
    { name: 'Buddhika' },
    { name: 'Nuwan' },
    { name: 'Isuru' },
    { name: 'Naleen' },
    { name: 'Asanka' },
  ];
}

home.component.html

<app-virtual-scroller [items]="items">
  • You may also look at [this article](https://blog.logrocket.com/virtual-scrolling-core-principles-and-basic-implementation-in-react/) I wrote in 2020, it's also for React, but might be helpful – dhilt Feb 13 '23 at 18:50
  • Hello, first of all, I would not recommend the setTimeout() in the ngAfterViewInit() lifeCycle method, you might want to simply listen to the Changes on your @Input(). Check this article maybe : https://ultimatecourses.com/blog/detect-input-property-changes-ngonchanges-setters – Alain Boudard Feb 14 '23 at 16:00

1 Answers1

0

playing with the different lifecycle methods, I came up with that :

https://stackblitz.com/edit/angular-rmxq8n?file=src/scroller/scroller.component.ts

Honestly, the calculation of the items in the list seems off, but I didn't take much time on it. You still have some basics here, and I hope it can help :)

Cheers ! Alain

Alain Boudard
  • 768
  • 6
  • 16
  • Appreciate your reply. But what I found problems in this implementation. 1.initially it loads all the list items in the DOM. 2.when I scroll downwards , it only remains the number of items fit to viewport size and I could't scroll up. But in the custom virtual scrollers they have above characteristics. for example https://www.npmjs.com/package/ngx-virtual-scroller – Gayan Buddhika Feb 15 '23 at 12:56
  • Absolutely ! I agree with you, there is no need to virtual scroll if the whole list is already loaded. And also agree with the second point. But at least you can check how we can organize some code between Inputs and lifecycle events, and it can be tricky sometimes :) – Alain Boudard Feb 15 '23 at 13:08