2

My page consists of several ng-select (Custom server-side search) dropdowns created by a *ngFor directive, having the possibility to pick multiple items from each dropdown.

I also want to include the virtual scroll functionality but I don't know how to make another server request and update the filterValues$ value to contain new data.

component.html

<ng-select [items]="filterValues$[filter.name] | async"
         [typeahead]="filterValuesInput$[filter.name]"
         [virtualScroll]="true"
         [multiple]="true"
         [closeOnSelect]="false"
         [loading]="filterValuesLoading[filter.name]"
         [(ngModel)]="filter.filter_values"
         (scrollToEnd)="onScrollToEnd(filter.name)"
         (open)="onFilterOpen(filter.name)"
         typeToSearchText="No values found"
         bindLabel="name">
</ng-select>

component.ts

onScrollToEnd(filterName) {
    this.fetchMore(filterName);
}

fetchMore(filterName) {
    this.filterValues$[filterName] = combineLatest(this.getFilterValues(filterName, this.afterKey), of(this.existingValues))
      .pipe(
        map(combined => {
          return combined[1].concat(combined[0])
        })
    );
}

getFilterValues(filterName, after) {
    return this.filterValuesInput$[filterName].pipe(
      tap(() => this.filterValuesLoading[filterName] = true),
      startWith(''),
      distinctUntilChanged(),
      switchMap(term  => this.search.getFilterValues(filterName, '' + term, '' + after).pipe(
        tap(res => {
          this.afterKey = res.after_key;
          this.filterValuesLoading[filterName] = false;
          this.existingValues = this.existingValues.concat(res.filter_values);
          this.totalFilterValues = res.total_hits;
          //this.bufferLength += this.initialValues.length;
        }),
        map(res => res.filter_values),
        catchError(() => of([])) // empty list on error
      ))
    )
}

Any help would be appreciated!

EDIT with updated code: I managed to implement the virtual scroll functionality but whenever I go to the bottom of the dropdown, it triggers the fetchMore() method and resets this.filterValues$[filterName] value, moving the dropdown from bottom to top. How can I prevent that?

shAkur
  • 944
  • 2
  • 21
  • 45
  • The only problem I notice, you didn't subscribe to fetchMore results. It's pretty difficult to figure out a problem without working example. Create such on https://stackblitz.com – shumih May 22 '20 at 12:55
  • Please check my updated question. Unfortunately I couldn't create a stackblitz. – shAkur May 22 '20 at 14:12
  • Try to set [trackByFn] function. It would help ng-select to manage changed items and might solve your problem – shumih May 22 '20 at 15:26

1 Answers1

0

I had the same problem.

Problem was that the ng-select tries to find the option of the selected value, which might not be available at the first load. You are loading for example first 15 values but the selected option is at a later position and even if you fetchMore it might still not be in the next fetch. So if you call next it sets you to the first position of the dropdown.

This might not be a problem if you are scrolling using the mouse wheel because it will automatically focus on where your mouse cursor is at. But if you are using the arrows it will always jump to the beginning(which was my problem).

My soluion was to add an aditional option(the selected one) always at the beginning of the option list(so ng-select could find it) + the rest of the list. The option might be then duplicated in the list, but its kind of user friendly to have the first option in the list the originally selected one.

Note that I am using ControlValueAccessor.

  protected _itemsBufferSource$ = new BehaviorSubject<unknown[]>([]);    
  public items$: Observable<unknown[]> = EMPTY;
  public input$ = new Subject<string>();
  public virtualScrollItems: unknown[] = [];
  public itemsBuffer$: Observable<unknown[]> = this._itemsBufferSource$.asObservable();

<ng-select
  #singleSelect
  [items]="(enableVirtualScrolling ? this.itemsBuffer$ : this.items$) | async"
  ...
  [(ngModel)]="valueChanged"
...

const firstValue:unknown[] = [this.valueChanged];
...
this._itemsBufferSource$.next(
          //add first value to beginning so it would find selected value and arrow virtual scrolling would work(filter out the nulls so there's no error)
          firstValue.concat(this.virtualScrollItems.slice(0, this.virtualScrollingBufferSize)).filter(x => x != null));
forestfly
  • 11
  • 3