简体   繁体   English

无限滚动用于Angular Material 6中的自动完成

[英]Infinite scroll for autocomplete in Angular Material 6

I'm trying to implement infinite scroll for autocomplete in angular material 6. My scenario is straightforward, I have an input filed with autocomplete enabled. 我正在尝试实现角度材料6中自动完成功能的无限滚动。我的场景很简单,我有一个启用了自动完成功能的输入文件。 when the user types, I will make the HTTP call with text in the input filed to show the results as suggestions. 当用户键入内容时,我将使用输入字段中的文本进行HTTP调用,以将结果显示为建议。 But I want to show only 25 suggestions, if results count more than 25 when user scroll to the bottom I want to add one more 25. Like this in angular 2 但我想只显示25个建议,如果算上结果大于25时,用户滚动到我想要的底部多加一个25像这样在角2

I was not able to find online. 我无法在网上找到。

Please advise or help me. 请指教或帮助我。 Thankyou in advance. 先感谢您。

 <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> 

I know the post is old, but i'm leaving the solution here in case anybody needs it. 我知道这个职位很旧,但是如果有人需要,我会把解决方案留在这里。

The trick is to get the reference to the mat-autocomplete panel's scrollbar. 诀窍是获取对mat-autocomplete面板的滚动条的引用。 I've done this using a custom directive: 我使用自定义指令完成了此操作:

 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 }); } } } } 

After this what remains is to load more data from the server when the scrollbar reaches 80% threshold: 之后,剩下的就是在滚动条达到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(); } } 

Note: I'm using a custom rxjs operator rxjs-take-while-inclusive. 注意:我使用的是自定义rxjs运算符rxjs-take-while-inclusive。

You case see it in action here: DEMO 您的案例在这里看到了实际效果: 演示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM