【问题标题】:Infinite scroll for autocomplete in Angular Material 6Angular Material 6中自动完成的无限滚动
【发布时间】:2018-12-19 23:52:28
【问题描述】:

我正在尝试在角度材料 6 中实现自动完成的无限滚动。我的场景很简单,我有一个启用了自动完成的输入文件。当用户键入时,我将在输入字段中使用文本进行 HTTP 调用,以将结果显示为建议。但是我只想显示 25 条建议,如果当用户滚动到底部时结果计数超过 25,我想再添加一个 25。 像 this 在角度 2 中

我在网上找不到。

请建议或帮助我。提前谢谢你。

<mat-form-field>
  <input matInput placeholder="Experiment Name" formControlName="experimentName" [matAutocomplete]="expNamesAutocomplete">
</mat-form-field>
<mat-autocomplete #expNamesAutocomplete="matAutocomplete">
  <mat-option *ngFor="let option of suggestedExpNames" [value]="option">
              {{ option }}
  </mat-option>
</mat-autocomplete>

【问题讨论】:

    标签: javascript html angular autocomplete angular-material2


    【解决方案1】:

    我知道这篇文章已经过时了,但我将把解决方案留在这里,以防有人需要。

    诀窍是获取对 mat-autocomplete 面板的滚动条的引用。我已经使用自定义指令完成了这项工作:

    import { Directive, ElementRef, EventEmitter, Input, Output, Host, Self, Optional, AfterViewInit, OnDestroy } from '@angular/core';
    import { MatAutocomplete } from '@angular/material';
    import { Observable, fromEvent, of, Subject, merge, combineLatest } from 'rxjs';
    import { map, startWith, switchMap, tap, debounceTime, filter, scan, withLatestFrom, mergeMap, takeUntil, takeWhile, distinctUntilChanged, skipUntil, exhaustMap, endWith } from 'rxjs/operators';
    import { takeWhileInclusive } from 'rxjs-take-while-inclusive';
    
    export interface IAutoCompleteScrollEvent {
      autoComplete: MatAutocomplete;
      scrollEvent: Event;
    }
    
    @Directive({
      selector: 'mat-autocomplete[optionsScroll]'
    })
    export class OptionsScrollDirective implements OnDestroy {
    
      @Input() thresholdPercent = .8;
      @Output('optionsScroll') scroll = new EventEmitter<IAutoCompleteScrollEvent>();
      _onDestroy = new Subject();
    
      constructor(public autoComplete: MatAutocomplete) {
        this.autoComplete.opened.pipe(
          tap(() => {
            // Note: When autocomplete raises opened, panel is not yet created (by Overlay)
            // Note: The panel will be available on next tick
            // Note: The panel wil NOT open if there are no options to display
            setTimeout(() => {
              // Note: remove listner just for safety, in case the close event is skipped.
              this.removeScrollEventListener();
              this.autoComplete.panel.nativeElement
                .addEventListener('scroll', this.onScroll.bind(this))
            });
          }),
          takeUntil(this._onDestroy)).subscribe();
    
        this.autoComplete.closed.pipe(
          tap(() => this.removeScrollEventListener()),
          takeUntil(this._onDestroy)).subscribe();
      }
    
      private removeScrollEventListener() {
        this.autoComplete.panel.nativeElement
          .removeEventListener('scroll', this.onScroll);
      }
    
      ngOnDestroy() {
        this._onDestroy.next();
        this._onDestroy.complete();
    
        this.removeScrollEventListener();
      }
    
      onScroll(event: Event) {
    
        if (this.thresholdPercent === undefined) {
          this.scroll.next({ autoComplete: this.autoComplete, scrollEvent: event });
        } else {
          const threshold = this.thresholdPercent * 100 * event.target.scrollHeight / 100;
          const current = event.target.scrollTop + event.target.clientHeight;
    
          //console.log(`scroll ${current}, threshold: ${threshold}`)
          if (current > threshold) {
            //console.log('load next page');
            this.scroll.next({ autoComplete: this.autoComplete, scrollEvent: event });
          }
        }
      }
    }

    之后剩下的就是在滚动条达到 80% 阈值时从服务器加载更多数据:

    import { Component, OnInit } from '@angular/core';
    import { FormControl } from '@angular/forms';
    import { Observable, fromEvent, of, Subject, merge, combineLatest } from 'rxjs';
    import { map, startWith, switchMap, tap, debounceTime, filter, scan, withLatestFrom, mergeMap, takeUntil, takeWhile, distinctUntilChanged, skipUntil, exhaustMap, endWith } from 'rxjs/operators';
    import { MatAutocomplete } from '@angular/material/autocomplete';
    import { takeWhileInclusive } from 'rxjs-take-while-inclusive';
    
    export interface ILookup {
      id: number,
      name: string
    }
    @Component({
      selector: 'autocomplete-filter-example',
      templateUrl: 'autocomplete-filter-example.html',
      styleUrls: ['autocomplete-filter-example.scss'],
    })
    export class AutocompleteFilterExample implements OnInit {
    
      searchText = new FormControl({ id: 2, name: 'ana' });
      filteredLookups$: Observable<ILookup[]>;
      private lookups: ILookup[] = [];
      private nextPage$ = new Subject();
      private _onDestroy = new Subject();
    
      // Fake backend api
      private getProducts(startsWith: string, page: number): Observable<ILookup[]> {
        console.log(`api call filter: ${startsWith}`);
    
        const take = 10;
        const skip = page > 0 ? (page - 1) * take : 0;
    
        const filtered = this.lookups
          .filter(option => option.name.toLowerCase().startsWith(startsWith.toLowerCase()))
    
        console.log(`skip: ${skip}, take: ${take}`);
    
        return of(filtered.slice(skip, skip + take));
      }
    
      ngOnInit() {
    
        // Note: Generate some mock data
        this.lookups = [{ id: 1994, name: 'ana' }, { id: 1989, name: 'narcis' }]
        for (let i = 1; i < 100; i++) {
          this.lookups.push({ id: i, name: 'test' + i })
        }
    
        // Note: listen for search text changes
        const filter$ = this.searchText.valueChanges.pipe(
          startWith(''),
          debounceTime(200),
          // Note: If the option valye is bound to object, after selecting the option
          // Note: the value will change from string to {}. We want to perform search 
          // Note: only when the type is string (no match)
          filter(q => typeof q === "string"));
    
        // Note: There are 2 stream here: the search text changes stream and the nextPage$ (raised by directive at 80% scroll)
        // Note: On every search text change, we issue a backend request starting the first page
        // Note: While the backend is processing our request we ignore any other NextPage emitts (exhaustMap).
        // Note: If in this time the search text changes, we don't need those results anymore (switchMap)
        this.filteredLookups$ = filter$.pipe(
          switchMap(filter => {
            //Note: Reset the page with every new seach text
            let currentPage = 1;
            return this.nextPage$.pipe(
              startWith(currentPage),
              //Note: Until the backend responds, ignore NextPage requests.
              exhaustMap(_ => this.getProducts(filter, currentPage)),
              tap(() => currentPage++),
              //Note: This is a custom operator because we also need the last emitted value.
              //Note: Stop if there are no more pages, or no results at all for the current search text.
              takeWhileInclusive(p => p.length > 0),
              scan((allProducts, newProducts) => allProducts.concat(newProducts), []),
            );
          })); // Note: We let asyncPipe subscribe.
    
      }
    
      displayWith(lookup) {
        return lookup ? lookup.name : null;
      }
    
      onScroll() {
        //Note: This is called multiple times after the scroll has reached the 80% threshold position.
        this.nextPage$.next();
      }
    
      ngOnDestroy() {
        this._onDestroy.next();
        this._onDestroy.complete();
      }
    }

    注意:我使用的是自定义 rxjs 运算符 rxjs-take-while-inclusive。

    您可以在这里看到它的实际效果:DEMO

    【讨论】:

    • 非常感谢您的回复,我已经做到了。 stackoverflow.com/questions/46347435/…
    • 如果有时间你能帮我在这篇文章netbasal.com/…stackblitz.com/edit/…中整合角度材料2虚拟卷轴
    • 我不认为可以通过扩展 mat-autcomplete 来完成。我们这里只做延迟加载,为了实现虚拟滚动(容器重用、墓碑等),您需要使用 cdk 虚拟滚动功能的不同类型的面板
    • @Zapacila 但是如何使用 MAT_AUTOCOMPLETE_SCROLL_STRATEGY 呢?
    猜你喜欢
    • 2017-04-16
    • 2021-05-31
    • 1970-01-01
    • 1970-01-01
    • 2016-10-11
    • 2020-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多