问题描述
我正在尝试在角度材料 6 中实现自动完成的无限滚动.我的场景很简单,我有一个启用了自动完成的输入文件.当用户键入时,我将使用输入字段中的文本进行 HTTP 调用,以将结果显示为建议.但我只想显示 25 条建议,如果当用户滚动到底部时结果计数超过 25,我想再添加 25 条.喜欢this在角度 2
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. when the user types, I will make the HTTP call with text in the input filed to show the results as suggestions. 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
我在网上找不到.
请指教或帮助我.提前谢谢你.
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.
诀窍是获取对 mat-autocomplete 面板滚动条的引用.我使用自定义指令完成了此操作:
The trick is to get the reference to the mat-autocomplete panel's scrollbar. 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 });
}
}
}
}
之后剩下的就是在滚动条达到 80% 阈值时从服务器加载更多数据:
After this what remains is to load more data from the server when the scrollbar reaches 80% threshold:
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.
Note: I'm using a custom rxjs operator rxjs-take-while-inclusive.
你可以在这里看到它的实际效果:演示
You case see it in action here: DEMO
这篇关于Angular Material 6 中用于自动完成的无限滚动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!