Search code examples
angularperformancerxjsobservableangular-ngselect

why is ng-select type ahead causing performance issues?


I am using ng-select with type-ahead filtering of items in the select list. When you type to filter the results for the first item in the list, the performance is lightning fast. After you select that first item, however, everything becomes extremely slow. I don't know rxjs well enough to understand what the bottleneck is.

My template:

<label>Select Employees:</label>
<ng-select [items]="people$ | async"
           bindLabel="full_name"
           [multiple]="true"
           [addTag]="addCustomItem"
           [hideSelected]="true"
           [trackByFn]="trackByFn"
           [minTermLength]="2"
           [loading]="peopleLoading"
           typeToSearchText="Please enter 2 or more characters (of first OR last name)"
           [typeahead]="peopleInput$"
           [(ngModel)]="selectedPersons">
</ng-select>
<br>
<div style="margin-bottom:100px">Selected persons: {{selectedPersons | json}}</div>

My Typescript component:

import { Component, OnInit } from '@angular/core';
import { concat, Observable, of, Subject } from 'rxjs';
import { Person, Employee } from './data.service';
import { catchError, distinctUntilChanged, switchMap, tap } from 'rxjs/operators';
import { NodeHTTPService } from '../../node-http.service';

@Component({
    selector: 'app-employee-picker',
    templateUrl: './employee-picker.component.html',
    styleUrls: ['./employee-picker.component.css']
})
export class EmployeePickerComponent implements OnInit {

people$: any/*Observable<Employee[]>*/;  // you can see I had an issue here.
peopleLoading = false;
peopleInput$ = new Subject<string>();
selectedPersons: Employee[];
addCustomItem: boolean = false;  // setting this to true allows users to add custom items not in the select list.

constructor(private _nodeHTTPService: NodeHTTPService) {
    }

ngOnInit() {
        this.loadPeople();
    }

trackByFn(item: Person) {
        return item.id;
    }

private loadPeople() {
        this.people$ = concat(
            of([]), // default items
            this.peopleInput$.pipe(
                distinctUntilChanged(),
                tap(() => this.peopleLoading = true),
                switchMap(term => this._nodeHTTPService.getUsers(term).pipe(
                    catchError(() => of([])), // empty list on error
                    tap(() => this.peopleLoading = false)
                ))
            )
        );
        console.log('this.people$: ', this.people$)
        }
    }

The NodeHttpService:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { CdfServiceService } from "./cdf-service.service";

@Injectable({
  providedIn: 'root'
})

export class NodeHTTPService {
  constructor(private http: HttpClient, private _cdfServiceService: CdfServiceService) { }

  getUsers(i) {
    if (!i) { i = ''};
    let users = this.http.get(`https://chq-smscqa01.chq.ei/db/users?user=${i}`)
    return users;
  }
}

I really appreciate any input on this question! Thank you.


Solution

  • There are two immediate things you can do to improve performance

    1) Implementing DOM recycling by enabling virtual scroll.

    As stated on the documentation, you can set the virtualScroll input property to true, which will only load the the specified number of DOM elements, which is based on the height of the current scroll container of the ng-select dropdown.

    2) Implementing pagination on your server-side. This will reduce the payload of each HTTP request, thus making it faster on both the frontend and the backend.