Scroll to the bottom to load more dynamic items through the API in Angular

275 Views Asked by At
<div class="col-6">
  <label>device</label>
  <div class="input-group" style="height: 400px; overflow-y: auto;" (scroll)="onScroll($event)">
    <ng-select
      formControlName="device_id"
      [items]="extractedOptions"
      bindLabel="label"
      bindValue="value"
      placeholder="Select device"
      appendTo="body"
      [multiple]="true"
    >
      <ng-template ng-label-tmp let-item="item">
        {{ item.label }}
      </ng-template>
      <ng-template ng-option-tmp let-item="item" let-search="searchTerm" let-index="index">
        {{ item.label }}
      </ng-template>
    </ng-select>
    
    <div style="color: red" *ngIf="shouldDisplayError('device_id') && hardwareform.get('device_id')!.hasError('required')">
      *Field is required
    </div>
  </div>
</div>

import { Component, ElementRef, HostListener, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { MatSnackBar } from '@angular/material/snack-bar';
import { DeviceService } from 'src/app/admin/services/device-device/device.service';
import { catchError, map } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';

@Component({
  selector: 'app-create-device',
  templateUrl: './create-device.component.html',
  styleUrls: ['./create-device.component.scss'],
})
export class DeviceComponent {
  @ViewChild('deviceSelect') deviceSelect!: ElementRef;
  deviceform!: FormGroup;
  loading: any;
  options: any = [];
  totalCount: any;
  extractedOptions: any;

  constructor(
    private fb: FormBuilder,
    private route: Router,
    private activatedRoute: ActivatedRoute,
    private _snackBar: MatSnackBar,
    private deviceService: DeviceService,
  ) {}

  title: string = 'New Device';
  page: number = 1;

  IsEdit: boolean = false;
  items_per_page: number = 10;

  ngOnInit() {
    this.loadOptions();
  }

  loadOptions() {
    this.loading = true;
    this.deviceService.getData(10, this.page).subscribe((response: any) => {
      const records = response.data.records;
      this.totalCount = response.data.count.total;
      this.extractedOptions = records.map((record: any) => ({
        label: record.name,
        value: record.id,
      }));

      this.options = this.options.concat(this.extractedOptions);

      this.page++;
      this.loading = false;
    });
  }
  
  shouldDisplayError(controlName: string): boolean {
    const control = this.deviceform.get(controlName);
    return control ? control.touched : false;
  }
 
  @HostListener('scroll',['$event'])
  onScroll(event: any) {
    console.log("testing")
    const target = event.target || event.srcElement;
    if (target.offsetHeight + target.scrollTop >= target.scrollHeight) {
      // Fetch more options when scrolled to the bottom
      this.loadOptions();
    }
  }
}

This is my HTML and TypeScript file. I am successfully fetching 10 records in my ng-select dropdown through these files. However, when I reach the bottom of the ng-options, the scroll event doesn't trigger. I want it to trigger the scroll event so that I can load more dynamic content into my ng-options. I had also checked the console for error messages, but no errors were reported.

1

There are 1 best solutions below

0
Naren Murali On

ng-select already has support for virtual scrolling. Please leverage this functionality to suit your code! Please find below a working example!

import { Component, ElementRef, ViewChild } from '@angular/core';
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { NgSelectModule } from '@ng-select/ng-select';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    FormsModule,
    NgSelectModule,
    CommonModule,
    ReactiveFormsModule,
    RouterModule,
  ],
  template: `
    <div class="col-6">
  <label>device</label>
  <div class="input-group" style="height:100vh;">
    <ng-select
           [virtualScroll]="true"
      [items]="extractedOptions"
      bindLabel="label"
      bindValue="id"
      placeholder="Select device"
           (scroll)="onScroll($event)"
           (scrollToEnd)="onScrollToEnd()"
      [multiple]="true"
    >
      <ng-template ng-label-tmp let-item="item">
        {{ item.label }}
      </ng-template>
      <ng-template ng-option-tmp let-item="item" let-search="searchTerm" let-index="index">
        {{ item.label }}
      </ng-template>
    </ng-select>
  </div>
</div>
  `,
})
export class App {
  @ViewChild('deviceSelect') deviceSelect!: ElementRef;
  deviceform!: FormGroup;
  loading: any;
  options: any = [];
  totalCount: any;
  extractedOptions: any = [];
  numberOfItemsFromEndBeforeFetchingMore = 10;

  constructor() {}

  title: string = 'New Device';
  page: number = 1;

  IsEdit: boolean = false;
  items_per_page: number = 10;

  ngOnInit() {
    this.loadOptions();
  }

  loadOptions() {
    console.log('asdf')
    this.loading = true;
    const start = this.page;
    const data = new Array(50).fill(null).map((item, i) => ({
      name: start + i,
      id: start + i,
    }));
    of({
      data: {
        records: data,
        count: {
          total: 100,
        },
      },
    })
      // .pipe(delay(1000))
      .subscribe((response: any) => {
        const records = response.data.records;
        const extractedOptions = records.map((record: any) => ({
          label: record.name,
          value: record.id,
        }));
        this.totalCount = response.data.count.total;

        this.extractedOptions.push(...extractedOptions);
        this.extractedOptions = [...this.extractedOptions]
        this.page++;
        this.loading = false;
      });
  }

  onScrollToEnd() {
    this.loadOptions();
  }

  onScroll(event: any) {
    console.log(event, this.extractedOptions.length);
    if (this.loading || this.extractedOptions.length > this.totalCount) {
      return;
    }
    if (event.end >= this.extractedOptions.length) {
      this.loadOptions();
    }
  }

  shouldDisplayError(controlName: string): boolean {
    const control = this.deviceform.get(controlName);
    return control ? control.touched : false;
  }

  // @HostListener('scroll', ['$event'])
  // onScroll(event: any) {
  //   console.log('testing');
  //   const target = event.target || event.srcElement;
  //   if (target.offsetHeight + target.scrollTop >= target.scrollHeight) {
  //     // Fetch more options when scrolled to the bottom
  //     this.loadOptions();
  //   }
  // }
}

bootstrapApplication(App);

stackblitz